prosicht 1.3.0 → 1.5.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ı ve sunum 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 CLI. Şablonlar [prosicht/ai-standards](https://github.com/prosicht/ai-standards) deposundan canlı olarak çekilir.
4
4
 
5
5
  ## Kullanım
6
6
 
@@ -10,31 +10,35 @@ npx prosicht init
10
10
  Önce ne yapmak istediğinizi sorar:
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
+ - **Blog:** Etkileşimli, animasyonlu blog yazısı altyapısı (çekirdek dosyalar, `AGENTS.md` ve çalışan bir örnek yazı) bulunduğunuz klasöre iner.
13
14
 
14
15
  Şablonu doğrudan vermek için:
15
16
  ```bash
16
17
  npx prosicht init -t web
17
18
  npx prosicht init -t presentation
19
+ npx prosicht init -t blog
18
20
  ```
19
21
 
20
22
  Güncelleme (şablon, `AGENTS.md` başındaki etiketten otomatik tespit edilir):
21
23
  ```bash
22
24
  npx prosicht update
23
25
  ```
24
- 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. Eski (1.x) klasör yapısındaki sunumlar onayla yeni yapıya taşınır (`--yes` sormadan taşır).
26
+ 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.
25
27
 
26
28
  ## Yayınlama
27
29
 
28
30
  ```bash
29
31
  npx prosicht publish
30
32
  ```
31
- 1. deck.prosicht.com'da hesap açın ve **Yeni sunum ekle** ile 32 karakterlik bir key alın.
32
- 2. Sunum klasöründe `npx prosicht publish` çalıştırın: **Sunum** → adres (varsayılan `deck.prosicht.com`) → key.
33
+ 1. deck.prosicht.com'da hesap açın ve **Yeni ekle** ile, klasörünüzle aynı türü (**Sunum** ya da **Blog yazısı**) seçerek 32 karakterlik bir key alın.
34
+ 2. Sunum veya blog klasöründe `npx prosicht publish` çalıştırın: **Sunum** / **Blog** (klasörden tespit edilip önceden seçilir) → adres (varsayılan `deck.prosicht.com`) → key.
33
35
  3. Link terminalde verilir. Key ve adres isterseniz `.prosicht/publish.json` içine kaydedilir (git dışı); sonraki yayınlar sormaz.
34
36
 
37
+ Tür yüklemeyle birlikte gönderilir (`manifest.kind`); sunucu ayrıca dosyalardan da anlar (`js/post.js` → blog, `js/scenes.js` → sunum). Hiç yayınlanmamış bir kayıt yüklenen türü alır. Daha önce sunum olarak yayınlanmış bir kaydın keyiyle blog yayınlanamaz (ya da tersi): CLI bunu açıkça söyler; panelden doğru türde yeni bir kayıt açın.
38
+
35
39
  Yalnızca `index.html`, `css/`, `js/` ve `assets/` yüklenir (en fazla 50 MB, dosya başına 25 MB, 500 dosya). `/` ile başlayan yollar için uyarı verilir. Kayıtlı key panelde yenilendiyse CLI yenisini sorar.
36
40
 
37
- Seçenekler: `--key <key>`, `--host <url>`, `-y` (soruları atla).
41
+ Seçenekler: `--key <key>`, `--host <url>`, `-y` (soruları atla; tür klasörden tespit edilir).
38
42
 
39
43
  ## Sunum hazırlama
40
44
 
@@ -45,12 +49,22 @@ python3 -m http.server 8000 # http://localhost:8000
45
49
  ```
46
50
  Ardından AI aracına konuyu söyleyin: *"KOBİ'ler için yapay zekâ konusunda 10 dakikalık sunum hazırla."*
47
51
 
52
+ ## Blog yazısı hazırlama
53
+
54
+ ```bash
55
+ mkdir yeni-yazi && cd yeni-yazi
56
+ npx prosicht init -t blog
57
+ python3 -m http.server 8000 # http://localhost:8000
58
+ ```
59
+ Görselleri `assets/images/` içine koyun ya da yolunu verin, sonra AI aracına konuyu söyleyin: *"Ev kahvesi demleme yöntemleri hakkında, gorseller/ klasöründeki fotoğrafları kullanarak etkileşimli bir blog yazısı hazırla."*
60
+
48
61
  ## Geliştirme
49
62
 
50
63
  Şablonları push etmeden yerel bir sunucudan denemek için (`PROSICHT_PUBLISH_URL` aynı şekilde yayın adresini değiştirir):
51
64
  ```bash
52
65
  cd ai-standards/templates && python3 -m http.server 8765
53
66
  PROSICHT_TEMPLATES_URL=http://127.0.0.1:8765 node /path/to/prosicht-cli/index.js init -t presentation
67
+ PROSICHT_TEMPLATES_URL=http://127.0.0.1:8765 node /path/to/prosicht-cli/index.js init -t blog
54
68
  ```
55
69
 
56
70
  Node.js 18 veya üstü gerekir.
package/index.js CHANGED
@@ -14,7 +14,8 @@ const BASE_URL = (process.env.PROSICHT_TEMPLATES_URL || 'https://raw.githubuserc
14
14
 
15
15
  const KINDS = {
16
16
  app: { title: 'Uygulama', description: 'Proje turune gore AGENTS.md (web, landing, mobile, backend)' },
17
- presentation: { title: 'Sunum', description: 'Etkilesimli, animasyonlu sunum altyapisi + AGENTS.md' }
17
+ presentation: { title: 'Sunum', description: 'Etkilesimli, animasyonlu sunum altyapisi + AGENTS.md' },
18
+ blog: { title: 'Blog', description: 'Etkilesimli, animasyonlu blog yazisi altyapisi + AGENTS.md' }
18
19
  };
19
20
 
20
21
  const APP_TEMPLATES = {
@@ -24,10 +25,35 @@ const APP_TEMPLATES = {
24
25
  backend: { name: 'Backend API Service', file: 'backend.md' }
25
26
  };
26
27
 
27
- // Sunum tek dosya degil, klasor: dosya listesi templates/presentation/manifest.json icinde
28
- const PRESENTATION = { key: 'presentation', name: 'Sunum Altyapisi', dir: 'presentation' };
28
+ // Sunum ve blog tek dosya degil, klasor: dosya listesi templates/<dir>/manifest.json icinde.
29
+ // owned: update'in dokunmadigi icerik dosyalari; migrate: kit 1.x klasorleri tasinabilir
30
+ const KITS = {
31
+ presentation: {
32
+ name: 'Sunum altyapisi',
33
+ dir: 'presentation',
34
+ migrate: true,
35
+ owned: 'Sunuma ait dosyalar (js/scenes.js, js/sims/, css/theme.css, css/sims/, assets/, sources.md) degistirilmedi.',
36
+ nextSteps: [
37
+ '1. python3 -m http.server 8000 -> http://localhost:8000 (ornek sunum)',
38
+ '2. AI aracina konuyu soyleyin: "<konu> hakkinda 10 dakikalik sunum hazirla"',
39
+ '3. npx prosicht publish -> deck.prosicht.com uzerinde yayinlayin'
40
+ ]
41
+ },
42
+ blog: {
43
+ name: 'Blog altyapisi',
44
+ dir: 'blog',
45
+ migrate: false,
46
+ owned: 'Yaziya ait dosyalar (js/post.js, js/sims/, css/theme.css, css/sims/, assets/, sources.md) degistirilmedi.',
47
+ nextSteps: [
48
+ '1. python3 -m http.server 8000 -> http://localhost:8000 (ornek yazi)',
49
+ '2. Gorselleri assets/images/ icine koyun ya da AI aracina yolunu verin',
50
+ '3. AI aracina konuyu soyleyin: "<konu> hakkinda, gorselleri kullanarak etkilesimli bir blog yazisi hazirla"',
51
+ '4. npx prosicht publish -> deck.prosicht.com uzerinde yayinlayin'
52
+ ]
53
+ }
54
+ };
29
55
 
30
- const TEMPLATE_KEYS = [...Object.keys(APP_TEMPLATES), PRESENTATION.key];
56
+ const TEMPLATE_KEYS = [...Object.keys(APP_TEMPLATES), ...Object.keys(KITS)];
31
57
  const TAG_PATTERN = /<!--\s*template:\s*(\w+)\s*-->\n?/;
32
58
 
33
59
  // Projedeki mevcut AGENTS.md dosyasından şablon türünü okur
@@ -80,11 +106,11 @@ async function askAppTemplate(message) {
80
106
  return response.template;
81
107
  }
82
108
 
83
- // Önce tür (Uygulama / Sunum), uygulamaysa ardından şablon sorulur
109
+ // Önce tür (Uygulama / Sunum / Blog), uygulamaysa ardından şablon sorulur
84
110
  async function askTemplate(message) {
85
111
  const kind = await askKind();
86
112
  if (!kind) return null;
87
- if (kind === 'presentation') return PRESENTATION.key;
113
+ if (KITS[kind]) return kind;
88
114
  return askAppTemplate(message);
89
115
  }
90
116
 
@@ -116,21 +142,34 @@ function resolveInsideCwd(relPath) {
116
142
  return target;
117
143
  }
118
144
 
119
- // init: çekirdek + başlangıç dosyaları (mevcut başlangıç dosyaları korunur)
120
- // update: yalnızca çekirdek dosyalar; sunum içeriğine dokunulmaz
121
- async function confirmMigration() {
122
- const response = await prompts({
123
- type: 'confirm',
124
- name: 'ok',
125
- initial: true,
126
- message: 'Bu sunum eski klasor yapisinda (kit 1.x). Yeni yapiya (index.html + css/ js/ assets/) tasinsin mi?'
127
- });
145
+ async function confirm(message, initial = true) {
146
+ const response = await prompts({ type: 'confirm', name: 'ok', initial, message });
128
147
  return Boolean(response.ok);
129
148
  }
130
149
 
131
- async function installPresentation(mode, options = {}) {
132
- const base = `${BASE_URL}/${PRESENTATION.dir}`;
133
- console.log(`\n[Pro Sicht] Sunum altyapisi ${mode === 'update' ? 'guncelleniyor' : 'indiriliyor'}...`);
150
+ // init: çekirdek + başlangıç dosyaları (mevcut başlangıç dosyaları korunur)
151
+ // update: yalnızca çekirdek dosyalar; sunum / yazı içeriğine dokunulmaz
152
+ async function installKit(key, mode, options = {}) {
153
+ const kit = KITS[key];
154
+ const base = `${BASE_URL}/${kit.dir}`;
155
+
156
+ // Sunum ve blog aynı çekirdek yollarını kullanır (index.html, js/core/app.js):
157
+ // başka bir kitin klasörüne yazmak onu bozar
158
+ const existing = detectExistingTemplate();
159
+ if (existing && existing !== key && (KITS[existing] || detectLayout(process.cwd()))) {
160
+ const label = KITS[existing] ? KITS[existing].name : `"${existing}" sablonu`;
161
+ if (options.yes) {
162
+ console.error(`Hata: Bu klasorde ${label} var; ${kit.name} cekirdek dosyalari ustune yazilmadi.`);
163
+ process.exitCode = 1;
164
+ return;
165
+ }
166
+ if (!(await confirm(`Bu klasorde ${label} var. ${kit.name} cekirdek dosyalari (index.html, js/core/ ...) ustune yazilsin mi?`, false))) {
167
+ console.log('Iptal edildi; hicbir dosya degistirilmedi.\n');
168
+ return;
169
+ }
170
+ }
171
+
172
+ console.log(`\n[Pro Sicht] ${kit.name} ${mode === 'update' ? 'guncelleniyor' : 'indiriliyor'}...`);
134
173
 
135
174
  try {
136
175
  const manifest = JSON.parse((await download(`${base}/manifest.json`)).toString('utf8'));
@@ -149,15 +188,15 @@ async function installPresentation(mode, options = {}) {
149
188
  const url = `${base}/${job.file.split('/').map(encodeURIComponent).join('/')}`;
150
189
  let data = await download(url);
151
190
  if (job.file === 'AGENTS.md') {
152
- data = Buffer.from(withTemplateTag(data.toString('utf8'), PRESENTATION.key), 'utf8');
191
+ data = Buffer.from(withTemplateTag(data.toString('utf8'), key), 'utf8');
153
192
  }
154
193
  return { ...job, data };
155
194
  }));
156
195
 
157
196
  // update: kit 1.x klasörünü önce yeni yapıya taşı (dosyalar indikten sonra, onayla).
158
197
  // Yalnızca uzaktaki kit de yeni yapıdaysa: eski manifest eski yollara yazar.
159
- if (mode === 'update' && detectLayout(process.cwd()) === 1 && Number(manifest.layout) >= 2) {
160
- if (!options.yes && !(await confirmMigration())) {
198
+ if (kit.migrate && mode === 'update' && detectLayout(process.cwd()) === 1 && Number(manifest.layout) >= 2) {
199
+ if (!options.yes && !(await confirm('Bu sunum eski klasor yapisinda (kit 1.x). Yeni yapiya (index.html + css/ js/ assets/) tasinsin mi?'))) {
161
200
  console.log('Iptal edildi; hicbir dosya degistirilmedi.\n');
162
201
  return;
163
202
  }
@@ -171,14 +210,13 @@ async function installPresentation(mode, options = {}) {
171
210
  }
172
211
  for (const job of kept) console.log(` = ${job.file} (mevcut, korundu)`);
173
212
 
174
- console.log(`Basarili: ${files.length} dosya yazildi (Sunum altyapisi v${manifest.version || '?'}).`);
213
+ console.log(`Basarili: ${files.length} dosya yazildi (${kit.name} v${manifest.version || '?'}).`);
175
214
  if (mode === 'init') {
176
215
  console.log('\nSonraki adimlar:');
177
- console.log(' 1. python3 -m http.server 8000 -> http://localhost:8000 (ornek sunum)');
178
- console.log(' 2. AI aracina konuyu soyleyin: "<konu> hakkinda 10 dakikalik sunum hazirla"');
179
- console.log(' 3. npx prosicht publish -> deck.prosicht.com uzerinde yayinlayin\n');
216
+ for (const step of kit.nextSteps) console.log(` ${step}`);
217
+ console.log('');
180
218
  } else {
181
- console.log('Sunuma ait dosyalar (js/scenes.js, js/sims/, css/theme.css, css/sims/, assets/, sources.md) degistirilmedi.\n');
219
+ console.log(`${kit.owned}\n`);
182
220
  }
183
221
  } catch (error) {
184
222
  console.error('Indirme hatasi:', error.message);
@@ -191,7 +229,7 @@ async function install(templateKey, mode, options = {}) {
191
229
  console.error(`Hata: Gecersiz sablon "${templateKey}". Gecerli: ${TEMPLATE_KEYS.join(', ')}`);
192
230
  process.exit(1);
193
231
  }
194
- if (templateKey === PRESENTATION.key) return installPresentation(mode, options);
232
+ if (KITS[templateKey]) return installKit(templateKey, mode, options);
195
233
  return fetchAndSaveTemplate(templateKey);
196
234
  }
197
235
 
@@ -202,7 +240,7 @@ program
202
240
 
203
241
  program
204
242
  .command('init')
205
- .description('Projeye AI standartlarini (AGENTS.md) veya sunum altyapisini indirir')
243
+ .description('Projeye AI standartlarini (AGENTS.md), sunum veya blog altyapisini indirir')
206
244
  .option('-t, --template <type>', `Sablon turu (${TEMPLATE_KEYS.join(', ')})`)
207
245
  .action(async (options) => {
208
246
  let templateKey = options.template;
@@ -217,9 +255,9 @@ program
217
255
 
218
256
  program
219
257
  .command('update')
220
- .description('Projedeki AGENTS.md (ve sunum altyapisinin cekirdek dosyalarini) tespit edip gunceller')
258
+ .description('Projedeki AGENTS.md (ve sunum / blog altyapisinin cekirdek dosyalarini) tespit edip gunceller')
221
259
  .option('-t, --template <type>', 'Sablon turunu zorla belirle')
222
- .option('-y, --yes', 'Eski sunum yapisini sormadan yeni yapiya tasi')
260
+ .option('-y, --yes', 'Sormadan devam et: eski sunum yapisini yeni yapiya tasir')
223
261
  .action(async (options) => {
224
262
  let templateKey = options.template;
225
263
 
@@ -240,10 +278,10 @@ program
240
278
 
241
279
  program
242
280
  .command('publish')
243
- .description('Sunumu deck.prosicht.com uzerinde yayinlar ve linki verir')
281
+ .description('Sunumu veya blog yazisini deck.prosicht.com uzerinde yayinlar ve linki verir')
244
282
  .option('-k, --key <key>', 'Panelden alinan 32 karakterlik yayin keyi')
245
283
  .option('--host <url>', 'Yayin adresi (varsayilan: https://deck.prosicht.com)')
246
- .option('-y, --yes', 'Sorulari atla: Sunum secilir, key kaydedilir')
284
+ .option('-y, --yes', 'Sorulari atla: tur klasorden tespit edilir, key kaydedilir')
247
285
  .action((options) => publish(options));
248
286
 
249
287
  program.parse(process.argv);
package/lib/files.js CHANGED
@@ -59,14 +59,23 @@ function findAbsolutePaths(files) {
59
59
  return hits;
60
60
  }
61
61
 
62
- // js/scenes.js içindeki deck.title (panelde ve paylaşım önizlemesinde görünür)
63
- function readDeckTitle(root) {
64
- const file = path.join(root, 'js', 'scenes.js');
62
+ // Başlık panelde ve paylaşım önizlemesinde görünür: sunumda js/scenes.js
63
+ // içindeki deck.title, blogda js/post.js içindeki post.title (ilk title alanı)
64
+ const CONTENT = { presentation: ['scenes.js', 'deck'], blog: ['post.js', 'post'] };
65
+
66
+ function readTitle(root, kind = 'presentation') {
67
+ const [fileName, name] = CONTENT[kind] || CONTENT.presentation;
68
+ const file = path.join(root, 'js', fileName);
65
69
  if (!fs.existsSync(file)) return null;
66
70
  const source = fs.readFileSync(file, 'utf8');
67
- const deck = source.match(/export\s+const\s+deck\s*=\s*\{([\s\S]*?)\};/);
68
- const title = deck && deck[1].match(/\btitle\s*:\s*(['"`])((?:(?!\1).)*)\1/);
69
- return title ? title[2].trim().slice(0, 120) : null;
71
+ // önce satır başındaki kapanış (iç içe nesneler), sonra tek satırlık yazım
72
+ const head = `export\\s+const\\s+${name}\\s*=\\s*\\{([\\s\\S]*?)`;
73
+ const block = source.match(new RegExp(`${head}\\n\\};`)) || source.match(new RegExp(`${head}\\};`));
74
+ const title = block && block[1].match(/\btitle\s*:\s*(['"`])((?:(?!\1).)*)\1/);
75
+ if (!title) return null;
76
+ // <br> ve *vurgu* işaretleri başlıkta görünmesin
77
+ const plain = title[2].replace(/<br\s*\/?>/gi, ' ').replace(/\*/g, '').replace(/\s+/g, ' ').trim();
78
+ return plain.slice(0, 120) || null;
70
79
  }
71
80
 
72
81
  function formatBytes(n) {
@@ -75,4 +84,4 @@ function formatBytes(n) {
75
84
  return `${(n / 1024 / 1024).toFixed(1)} MB`;
76
85
  }
77
86
 
78
- module.exports = { collectFiles, findAbsolutePaths, readDeckTitle, formatBytes, MIME_TYPES };
87
+ module.exports = { collectFiles, findAbsolutePaths, readTitle, formatBytes, MIME_TYPES };
package/lib/layout.js CHANGED
@@ -1,8 +1,8 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
- // Sunum klasör yapıları: 1 = kök dizinde app.js/sims/styles (kit 1.x),
5
- // 2 = index.html + css/ js/ assets/ (kit 2.x). 0 = sunum değil.
4
+ // Kit klasör yapıları: 1 = kök dizinde app.js/sims/styles (sunum kit 1.x),
5
+ // 2 = index.html + css/ js/ assets/ (sunum kit 2.x ve blog). 0 = kit değil.
6
6
  function detectLayout(root) {
7
7
  const has = (rel) => fs.existsSync(path.join(root, rel));
8
8
  if (has('js/core/app.js')) return 2;
@@ -10,6 +10,14 @@ function detectLayout(root) {
10
10
  return 0;
11
11
  }
12
12
 
13
+ // 2.x klasörünün türü: içerik dosyasından (blog: js/post.js, sunum: js/scenes.js)
14
+ function detectKit(root) {
15
+ const has = (rel) => fs.existsSync(path.join(root, rel));
16
+ if (has('js/post.js')) return 'blog';
17
+ if (has('js/scenes.js')) return 'presentation';
18
+ return null;
19
+ }
20
+
13
21
  // Proje klasöründeki .gitignore'a satır ekler (yoksa oluşturur)
14
22
  function ensureGitignore(root, line) {
15
23
  const file = path.join(root, '.gitignore');
@@ -20,4 +28,4 @@ function ensureGitignore(root, line) {
20
28
  return true;
21
29
  }
22
30
 
23
- module.exports = { detectLayout, ensureGitignore };
31
+ module.exports = { detectLayout, detectKit, ensureGitignore };
package/lib/publish.js CHANGED
@@ -1,8 +1,8 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const prompts = require('prompts');
4
- const { detectLayout, ensureGitignore } = require('./layout');
5
- const { collectFiles, findAbsolutePaths, readDeckTitle, formatBytes, MIME_TYPES } = require('./files');
4
+ const { detectLayout, detectKit, ensureGitignore } = require('./layout');
5
+ const { collectFiles, findAbsolutePaths, readTitle, formatBytes, MIME_TYPES } = require('./files');
6
6
 
7
7
  const DEFAULT_HOST = (process.env.PROSICHT_PUBLISH_URL || 'https://deck.prosicht.com').replace(/\/+$/, '');
8
8
  const KEY_PATTERN = /^[A-Za-z0-9]{32}$/;
@@ -75,9 +75,10 @@ async function chooseKey(saved, flag) {
75
75
  });
76
76
  }
77
77
 
78
- async function upload(host, key, files, title) {
78
+ // Sunucu türü dosyalardan da anlar; kind yine de gönderilir (panelde rozet, MCP'de doğru kit)
79
+ async function upload(host, key, files, title, kind) {
79
80
  const form = new FormData();
80
- form.append('manifest', JSON.stringify({ title, files: files.map((f) => ({ path: f.path, size: f.size })) }));
81
+ form.append('manifest', JSON.stringify({ title, kind, files: files.map((f) => ({ path: f.path, size: f.size })) }));
81
82
  for (const f of files) {
82
83
  const type = MIME_TYPES[path.extname(f.path).toLowerCase()];
83
84
  form.append('files', new Blob([fs.readFileSync(f.abs)], { type }), path.basename(f.path));
@@ -93,9 +94,19 @@ async function upload(host, key, files, title) {
93
94
  return { status: response.status, body };
94
95
  }
95
96
 
97
+ const NOUN = { presentation: 'sunum', blog: 'blog yazisi' };
98
+ const DATIVE = { presentation: 'sunuma', blog: 'blog yazisina' };
99
+ const PANEL_CHOICE = { presentation: 'Sunum', blog: 'Blog yazisi' };
100
+
96
101
  function explain(status, body) {
97
102
  if (status === 401) fail('Key gecersiz veya yenilenmis. Panelden guncel keyi alin.');
98
- if (status === 413) fail('Sunum boyut sinirini asiyor (en fazla 50 MB).');
103
+ // key, daha önce başka türde yayınlanmış bir içeriğe ait
104
+ if (status === 409 && DATIVE[body.deckKind] && NOUN[body.kind]) {
105
+ fail(`Bu key bir ${DATIVE[body.deckKind]} ait; bu klasordeki icerik bir ${NOUN[body.kind]}.`, [
106
+ `Panelde "Yeni ekle" ile ${PANEL_CHOICE[body.kind]} secip yeni bir key alin ve --key ile verin.`
107
+ ]);
108
+ }
109
+ if (status === 413) fail('Dosyalar boyut sinirini asiyor (en fazla 50 MB).');
99
110
  if (status === 400 || status === 422) fail(body.error || 'Dosyalar reddedildi.', body.details || []);
100
111
  fail(`Sunucu hatasi (HTTP ${status})${body.error ? `: ${body.error}` : ''}`);
101
112
  }
@@ -103,18 +114,26 @@ function explain(status, body) {
103
114
  async function publish(options = {}) {
104
115
  const root = process.cwd();
105
116
 
106
- const kind = options.yes ? 'presentation' : await ask({
117
+ const detected = detectKit(root);
118
+ const choices = [
119
+ { title: 'Sunum', value: 'presentation' },
120
+ { title: 'Blog', value: 'blog' },
121
+ { title: 'Uygulama', value: 'app', disabled: true, description: 'yakinda' }
122
+ ];
123
+ const kind = options.yes ? (detected || 'presentation') : await ask({
107
124
  type: 'select', name: 'kind', message: 'Ne yayinlamak istiyorsunuz?',
108
- choices: [
109
- { title: 'Sunum', value: 'presentation' },
110
- { title: 'Uygulama', value: 'app', disabled: true, description: 'yakinda' }
111
- ]
125
+ choices,
126
+ initial: Math.max(0, choices.findIndex((c) => c.value === detected))
112
127
  });
113
- if (kind !== 'presentation') process.exit(0);
128
+ if (kind !== 'presentation' && kind !== 'blog') process.exit(0);
129
+ const label = kind === 'blog' ? 'blog yazisi' : 'sunum';
114
130
 
115
131
  const layout = detectLayout(root);
116
132
  if (layout === 1) fail('Bu sunum eski klasor yapisinda. Once `npx prosicht update` ile yeni yapiya tasiyin.');
117
- if (layout === 0) fail('Bu klasorde bir sunum bulunamadi (index.html ve js/core/app.js gerekli). Sunum klasorunde calistirin.');
133
+ if (layout === 0) fail(`Bu klasorde bir ${label} bulunamadi (index.html ve js/core/app.js gerekli). ${kind === 'blog' ? 'Blog' : 'Sunum'} klasorunde calistirin.`);
134
+ if (detected && detected !== kind) {
135
+ fail(`Bu klasordeki icerik bir ${detected === 'blog' ? 'blog yazisi' : 'sunum'}; "${kind === 'blog' ? 'Blog' : 'Sunum'}" secildi.`);
136
+ }
118
137
 
119
138
  const { files, total, errors } = collectFiles(root);
120
139
  if (errors.length) fail('Yayin oncesi kontrol basarisiz:', errors);
@@ -129,11 +148,11 @@ async function publish(options = {}) {
129
148
  let key = await chooseKey(options.key ? null : config.key, options.key);
130
149
  if (!key) process.exit(0);
131
150
 
132
- const title = readDeckTitle(root);
151
+ const title = readTitle(root, kind);
133
152
  const send = async () => {
134
153
  console.log(`\n[Pro Sicht] ${files.length} dosya (${formatBytes(total)}) yukleniyor...`);
135
154
  try {
136
- return await upload(host, key, files, title);
155
+ return await upload(host, key, files, title, kind);
137
156
  } catch (error) {
138
157
  return fail(`${host} adresine ulasilamadi: ${error.cause?.code || error.message}`);
139
158
  }
@@ -149,7 +168,7 @@ async function publish(options = {}) {
149
168
  if (result.status !== 200 || !result.body.url) explain(result.status, result.body);
150
169
 
151
170
  const { url, privacy, version } = result.body;
152
- console.log(`Yayinlandi (surum ${version}): ${url}`);
171
+ console.log(`Yayinlandi (${label}, surum ${version}): ${url}`);
153
172
  if (privacy === 'private') {
154
173
  console.log('Gizlilik: yalnizca siz gorebilirsiniz. Paylasmak icin panelden "Baglantiya sahip herkes" secin.');
155
174
  } else {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "prosicht",
3
- "version": "1.3.0",
4
- "description": "Pro Sicht AI Standards CLI: AGENTS.md sablonlari ve sunum altyapisi",
3
+ "version": "1.5.0",
4
+ "description": "Pro Sicht AI Standards CLI: AGENTS.md sablonlari, sunum ve blog altyapisi",
5
5
  "main": "index.js",
6
6
  "bin": {
7
7
  "prosicht": "./index.js"
@@ -21,7 +21,8 @@
21
21
  "agents",
22
22
  "cli",
23
23
  "prosicht",
24
- "presentation"
24
+ "presentation",
25
+ "blog"
25
26
  ],
26
27
  "author": "Pro Sicht",
27
28
  "license": "MIT",