prosicht 1.6.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 +36 -1
- package/index.js +50 -14
- package/lib/beyin.js +515 -0
- package/lib/obsidian.js +193 -0
- package/lib/overrides.js +42 -0
- package/lib/system.js +58 -0
- package/package.json +6 -4
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):
|
|
@@ -25,6 +27,37 @@ npx prosicht update
|
|
|
25
27
|
```
|
|
26
28
|
Sunum klasörlerinde `update` yalnızca çekirdek dosyaları (`index.html`, `css/core/`, `js/core/`, `AGENTS.md`) yeniler; `js/scenes.js`, `js/sims/`, `css/theme.css`, `css/sims/`, `assets/` ve `sources.md` dokunulmadan kalır. Blog klasörlerinde de yalnızca çekirdek yenilenir (`js/post.js`, `js/sims/`, `css/theme.css`, `css/sims/`, `assets/`, `sources.md` dokunulmaz). Eski (1.x) klasör yapısındaki sunumlar onayla yeni yapıya taşınır (`--yes` sormadan taşır). Sunum ve blog aynı çekirdek yollarını kullanır: bir kitin klasörüne diğeri yazılmadan önce onay istenir, `--yes` ile bu durumda işlem yapılmaz.
|
|
27
29
|
|
|
30
|
+
### Projeye özel kurallar
|
|
31
|
+
|
|
32
|
+
`init` ve `update`, `AGENTS.md`'yi şablondan baştan yazar. Projeye özel kuralları (ör. Next.js şablonunu Nuxt'a uyarlayan eşlemeler) şu işaretler arasına yazın; CLI bu bloğu olduğu gibi korur ve yeni şablonda ilk `## ` başlığının önüne (başlık ve giriş paragrafından sonra) koyar:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
<!-- BEGIN:project-overrides -->
|
|
36
|
+
## 0. Project Overrides
|
|
37
|
+
- ...
|
|
38
|
+
<!-- END:project-overrides -->
|
|
39
|
+
```
|
|
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+).
|
|
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
|
+
|
|
28
61
|
## Yayınlama
|
|
29
62
|
|
|
30
63
|
```bash
|
|
@@ -76,4 +109,6 @@ PROSICHT_TEMPLATES_URL=http://127.0.0.1:8765 node /path/to/prosicht-cli/index.js
|
|
|
76
109
|
PROSICHT_TEMPLATES_URL=http://127.0.0.1:8765 node /path/to/prosicht-cli/index.js init -t blog
|
|
77
110
|
```
|
|
78
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
|
+
|
|
79
114
|
Node.js 18 veya üstü gerekir.
|
package/index.js
CHANGED
|
@@ -8,6 +8,9 @@ const { version } = require('./package.json');
|
|
|
8
8
|
const { detectLayout } = require('./lib/layout');
|
|
9
9
|
const { migrateV1 } = require('./lib/migrate');
|
|
10
10
|
const { publish } = require('./lib/publish');
|
|
11
|
+
const { insertOverrides, readExistingOverrides } = require('./lib/overrides');
|
|
12
|
+
const { initBeyin, updateBeyin, installedVersion } = require('./lib/beyin');
|
|
13
|
+
const { confirm } = require('./lib/system');
|
|
11
14
|
|
|
12
15
|
// PROSICHT_TEMPLATES_URL: sablonlari push etmeden yerel bir sunucudan denemek icin
|
|
13
16
|
const BASE_URL = (process.env.PROSICHT_TEMPLATES_URL || 'https://raw.githubusercontent.com/ProSicht/ai-standards/main/templates').replace(/\/+$/, '');
|
|
@@ -15,7 +18,8 @@ const BASE_URL = (process.env.PROSICHT_TEMPLATES_URL || 'https://raw.githubuserc
|
|
|
15
18
|
const KINDS = {
|
|
16
19
|
app: { title: 'Uygulama', description: 'Proje turune gore AGENTS.md (web, landing, mobile, backend)' },
|
|
17
20
|
presentation: { title: 'Sunum', description: 'Etkilesimli, animasyonlu sunum altyapisi + AGENTS.md' },
|
|
18
|
-
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)' }
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
const APP_TEMPLATES = {
|
|
@@ -53,7 +57,7 @@ const KITS = {
|
|
|
53
57
|
}
|
|
54
58
|
};
|
|
55
59
|
|
|
56
|
-
const TEMPLATE_KEYS = [...Object.keys(APP_TEMPLATES), ...Object.keys(KITS)];
|
|
60
|
+
const TEMPLATE_KEYS = [...Object.keys(APP_TEMPLATES), ...Object.keys(KITS), 'beyin'];
|
|
57
61
|
const TAG_PATTERN = /<!--\s*template:\s*(\w+)\s*-->\n?/;
|
|
58
62
|
|
|
59
63
|
// Projedeki mevcut AGENTS.md dosyasından şablon türünü okur
|
|
@@ -71,6 +75,26 @@ function withTemplateTag(content, templateKey) {
|
|
|
71
75
|
return `<!-- template: ${templateKey} -->\n` + content.replace(TAG_PATTERN, '');
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
// Yeni AGENTS.md: şablon + etiket + projedeki mevcut AGENTS.md'nin proje bloğu (varsa)
|
|
79
|
+
function buildAgentsMd(content, templateKey, overrides) {
|
|
80
|
+
return withTemplateTag(overrides ? insertOverrides(content, overrides) : content, templateKey);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Mevcut AGENTS.md'nin proje bloğu; işaretler bozuksa hatayı yazar ve undefined döner (hiçbir şey yazılmaz)
|
|
84
|
+
function loadOverrides() {
|
|
85
|
+
try {
|
|
86
|
+
return readExistingOverrides(process.cwd());
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`Hata: ${error.message} AGENTS.md degistirilmedi.`);
|
|
89
|
+
process.exitCode = 1;
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function reportOverrides(overrides) {
|
|
95
|
+
if (overrides) console.log('Projeye ozel blok (project-overrides) korundu.');
|
|
96
|
+
}
|
|
97
|
+
|
|
74
98
|
async function download(url) {
|
|
75
99
|
const response = await fetch(url);
|
|
76
100
|
if (!response.ok) {
|
|
@@ -106,25 +130,28 @@ async function askAppTemplate(message) {
|
|
|
106
130
|
return response.template;
|
|
107
131
|
}
|
|
108
132
|
|
|
109
|
-
// Önce tür (Uygulama / Sunum / Blog), uygulamaysa ardından şablon sorulur
|
|
133
|
+
// Önce tür (Uygulama / Sunum / Blog / Beyin), uygulamaysa ardından şablon sorulur
|
|
110
134
|
async function askTemplate(message) {
|
|
111
135
|
const kind = await askKind();
|
|
112
136
|
if (!kind) return null;
|
|
113
|
-
if (KITS[kind]) return kind;
|
|
137
|
+
if (KITS[kind] || kind === 'beyin') return kind;
|
|
114
138
|
return askAppTemplate(message);
|
|
115
139
|
}
|
|
116
140
|
|
|
117
141
|
async function fetchAndSaveTemplate(templateKey) {
|
|
118
142
|
const selected = APP_TEMPLATES[templateKey];
|
|
119
143
|
const fileUrl = `${BASE_URL}/${selected.file}`;
|
|
144
|
+
const overrides = loadOverrides();
|
|
145
|
+
if (overrides === undefined) return;
|
|
120
146
|
console.log(`\n[Pro Sicht] "${selected.name}" sablonu indiriliyor...`);
|
|
121
147
|
|
|
122
148
|
try {
|
|
123
149
|
const content = (await download(fileUrl)).toString('utf8');
|
|
124
150
|
const targetPath = path.join(process.cwd(), 'AGENTS.md');
|
|
125
|
-
fs.writeFileSync(targetPath,
|
|
151
|
+
fs.writeFileSync(targetPath, buildAgentsMd(content, templateKey, overrides), 'utf8');
|
|
126
152
|
|
|
127
153
|
console.log(`Basarili: AGENTS.md kaydedildi! (Sablon: ${templateKey})`);
|
|
154
|
+
reportOverrides(overrides);
|
|
128
155
|
console.log(`Konum: ${targetPath}\n`);
|
|
129
156
|
} catch (error) {
|
|
130
157
|
console.error('Indirme hatasi:', error.message);
|
|
@@ -142,11 +169,6 @@ function resolveInsideCwd(relPath) {
|
|
|
142
169
|
return target;
|
|
143
170
|
}
|
|
144
171
|
|
|
145
|
-
async function confirm(message, initial = true) {
|
|
146
|
-
const response = await prompts({ type: 'confirm', name: 'ok', initial, message });
|
|
147
|
-
return Boolean(response.ok);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
172
|
// init: çekirdek + başlangıç dosyaları (mevcut başlangıç dosyaları korunur)
|
|
151
173
|
// update: yalnızca çekirdek dosyalar; sunum / yazı içeriğine dokunulmaz
|
|
152
174
|
async function installKit(key, mode, options = {}) {
|
|
@@ -169,6 +191,8 @@ async function installKit(key, mode, options = {}) {
|
|
|
169
191
|
}
|
|
170
192
|
}
|
|
171
193
|
|
|
194
|
+
const overrides = loadOverrides();
|
|
195
|
+
if (overrides === undefined) return;
|
|
172
196
|
console.log(`\n[Pro Sicht] ${kit.name} ${mode === 'update' ? 'guncelleniyor' : 'indiriliyor'}...`);
|
|
173
197
|
|
|
174
198
|
try {
|
|
@@ -188,7 +212,7 @@ async function installKit(key, mode, options = {}) {
|
|
|
188
212
|
const url = `${base}/${job.file.split('/').map(encodeURIComponent).join('/')}`;
|
|
189
213
|
let data = await download(url);
|
|
190
214
|
if (job.file === 'AGENTS.md') {
|
|
191
|
-
data = Buffer.from(
|
|
215
|
+
data = Buffer.from(buildAgentsMd(data.toString('utf8'), key, overrides), 'utf8');
|
|
192
216
|
}
|
|
193
217
|
return { ...job, data };
|
|
194
218
|
}));
|
|
@@ -211,6 +235,7 @@ async function installKit(key, mode, options = {}) {
|
|
|
211
235
|
for (const job of kept) console.log(` = ${job.file} (mevcut, korundu)`);
|
|
212
236
|
|
|
213
237
|
console.log(`Basarili: ${files.length} dosya yazildi (${kit.name} v${manifest.version || '?'}).`);
|
|
238
|
+
if (files.some((file) => file.file === 'AGENTS.md')) reportOverrides(overrides);
|
|
214
239
|
if (mode === 'init') {
|
|
215
240
|
console.log('\nSonraki adimlar:');
|
|
216
241
|
for (const step of kit.nextSteps) console.log(` ${step}`);
|
|
@@ -229,6 +254,7 @@ async function install(templateKey, mode, options = {}) {
|
|
|
229
254
|
console.error(`Hata: Gecersiz sablon "${templateKey}". Gecerli: ${TEMPLATE_KEYS.join(', ')}`);
|
|
230
255
|
process.exit(1);
|
|
231
256
|
}
|
|
257
|
+
if (templateKey === 'beyin') return mode === 'init' ? initBeyin(options) : updateBeyin(options);
|
|
232
258
|
if (KITS[templateKey]) return installKit(templateKey, mode, options);
|
|
233
259
|
return fetchAndSaveTemplate(templateKey);
|
|
234
260
|
}
|
|
@@ -240,8 +266,11 @@ program
|
|
|
240
266
|
|
|
241
267
|
program
|
|
242
268
|
.command('init')
|
|
243
|
-
.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')
|
|
244
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')
|
|
245
274
|
.action(async (options) => {
|
|
246
275
|
let templateKey = options.template;
|
|
247
276
|
|
|
@@ -250,7 +279,9 @@ program
|
|
|
250
279
|
if (!templateKey) process.exit(0);
|
|
251
280
|
}
|
|
252
281
|
|
|
253
|
-
|
|
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 : {});
|
|
254
285
|
});
|
|
255
286
|
|
|
256
287
|
program
|
|
@@ -258,10 +289,15 @@ program
|
|
|
258
289
|
.description('Projedeki AGENTS.md (ve sunum / blog altyapisinin cekirdek dosyalarini) tespit edip gunceller')
|
|
259
290
|
.option('-t, --template <type>', 'Sablon turunu zorla belirle')
|
|
260
291
|
.option('-y, --yes', 'Sormadan devam et: eski sunum yapisini yeni yapiya tasir')
|
|
292
|
+
.option('--vault <yol>', 'Beyin: guncellenecek kasa klasoru')
|
|
261
293
|
.action(async (options) => {
|
|
262
294
|
let templateKey = options.template;
|
|
263
295
|
|
|
264
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
|
+
}
|
|
265
301
|
if (!templateKey) {
|
|
266
302
|
templateKey = detectExistingTemplate();
|
|
267
303
|
if (templateKey) {
|
|
@@ -273,7 +309,7 @@ program
|
|
|
273
309
|
}
|
|
274
310
|
}
|
|
275
311
|
|
|
276
|
-
await install(templateKey, 'update', { yes: options.yes });
|
|
312
|
+
await install(templateKey, 'update', { yes: options.yes, vault: options.vault });
|
|
277
313
|
});
|
|
278
314
|
|
|
279
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
|
+
};
|
package/lib/obsidian.js
ADDED
|
@@ -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/overrides.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// Projeye özel kurallar AGENTS.md içinde bu işaretler arasında durur; init ve update
|
|
5
|
+
// şablonu yeniden yazarken bloğu olduğu gibi korur (ör. Next.js şablonunu Nuxt'a uyarlayan kurallar)
|
|
6
|
+
const BEGIN = '<!-- BEGIN:project-overrides -->';
|
|
7
|
+
const END = '<!-- END:project-overrides -->';
|
|
8
|
+
|
|
9
|
+
function count(text, marker) {
|
|
10
|
+
return text.split(marker).length - 1;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Bloğu (işaretler dahil) döndürür; blok yoksa null. Bozuk işaretlerde hata fırlatır:
|
|
14
|
+
// yazmaya devam etmek bloğu sessizce siler.
|
|
15
|
+
function extractOverrides(content) {
|
|
16
|
+
const begins = count(content, BEGIN);
|
|
17
|
+
const ends = count(content, END);
|
|
18
|
+
if (begins === 0 && ends === 0) return null;
|
|
19
|
+
const start = content.indexOf(BEGIN);
|
|
20
|
+
const end = content.indexOf(END);
|
|
21
|
+
if (begins !== 1 || ends !== 1 || end < start) {
|
|
22
|
+
throw new Error(`AGENTS.md icindeki proje blogu bozuk: tam olarak bir "${BEGIN}" ve ondan sonra bir "${END}" olmali.`);
|
|
23
|
+
}
|
|
24
|
+
return content.slice(start, end + END.length);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Bloğu şablonun ilk "## " başlığının önüne koyar (başlık ve giriş paragrafından sonra);
|
|
28
|
+
// başlık yoksa sona ekler
|
|
29
|
+
function insertOverrides(content, block) {
|
|
30
|
+
const heading = content.search(/^## /m);
|
|
31
|
+
if (heading === -1) return `${content.replace(/\n*$/, '')}\n\n${block}\n`;
|
|
32
|
+
return `${content.slice(0, heading)}${block}\n\n${content.slice(heading)}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Projedeki mevcut AGENTS.md'den blok; dosya yoksa null
|
|
36
|
+
function readExistingOverrides(root) {
|
|
37
|
+
const file = path.join(root, 'AGENTS.md');
|
|
38
|
+
if (!fs.existsSync(file)) return null;
|
|
39
|
+
return extractOverrides(fs.readFileSync(file, 'utf8'));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = { BEGIN, END, extractOverrides, insertOverrides, readExistingOverrides };
|
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.
|
|
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"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"node": ">=18"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"test": "
|
|
17
|
+
"test": "node --test"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"ai",
|
|
@@ -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",
|