natureco-cli 5.20.4 → 5.21.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/CHANGELOG.md +779 -750
- package/README.md +608 -610
- package/bin/natureco.js +1099 -1095
- package/package.json +95 -94
- package/scripts/generate-qr.js +21 -0
- package/scripts/import-curated-skills-log.json +5 -0
- package/scripts/import-curated-skills.js +262 -0
- package/scripts/import-skills-log.json +167 -0
- package/scripts/import-skills.js +261 -0
- package/scripts/postinstall.js +125 -0
- package/src/commands/agent.js +280 -280
- package/src/commands/ask.js +4 -2
- package/src/commands/chat.js +0 -1
- package/src/commands/help.js +0 -2
- package/src/commands/naturehub.js +206 -373
- package/src/commands/repl.js +52 -62
- package/src/providers/mem0-memory.js +121 -121
- package/src/providers/supermemory-memory.js +117 -117
- package/src/tools/llm_task.js +150 -163
- package/src/tools/mac_alarm.js +199 -199
- package/src/tools/workflow.js +424 -439
- package/src/utils/api.js +1211 -1188
- package/src/utils/cost-tracker.js +361 -360
- package/src/utils/inquirer-wrapper.js +43 -31
- package/src/utils/paste-safe-input.js +346 -334
- package/src/utils/process-errors.js +129 -115
- package/src/utils/provider-detect.js +76 -72
- package/src/utils/skills.js +1 -1
- package/src/utils/system-prompt.js +136 -136
- package/src/utils/tools.js +324 -324
- package/README.md.bak +0 -565
- package/src/tools/http.js.bak +0 -78
|
@@ -1,334 +1,346 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* paste-safe-input — Terminal'e uzun/çok satırlı metin yapıştırılınca
|
|
3
|
-
* her satırın ayrı bir "Enter" gibi gönderilip anında submit edilmesini önler.
|
|
4
|
-
*
|
|
5
|
-
* Sorun: Node'un düz `readline` arayüzü, terminalden gelen her "\n" karakterini
|
|
6
|
-
* bir 'line' event'i olarak görür. Terminal "bracketed paste mode" açık değilse
|
|
7
|
-
* (veya açık olsa da bu karakterler ayıklanmazsa), kullanıcı 10 satırlık bir metni
|
|
8
|
-
* yapıştırdığında readline bunu 10 ayrı mesaj gibi okur ve her birini hemen
|
|
9
|
-
* gönderir — kullanıcı paste'i bitirmeden cevaplar gelmeye başlar.
|
|
10
|
-
*
|
|
11
|
-
* Çözüm: İki katmanlı bir yaklaşım:
|
|
12
|
-
*
|
|
13
|
-
* 1. INPUT KATMANI (createPasteSafeInput):
|
|
14
|
-
* Terminalin "bracketed paste" (ESC[200~ ... ESC[201~) işaretleyicilerini
|
|
15
|
-
* dinleyip, bu işaretler arasındaki tüm veriyi tek parça olarak topluyoruz.
|
|
16
|
-
* Ayrıca terminal-agnostik bir heuristik: çok satırlı büyük chunk'ları da
|
|
17
|
-
* paste olarak algılar. İçindeki gerçek satır sonlarını geçici bir
|
|
18
|
-
* placeholder (NEWLINE_PLACEHOLDER) ile değiştirip readline'a TEK satır gibi
|
|
19
|
-
* besliyoruz. Kullanıcı gerçekten Enter'a basana kadar hiçbir 'line' event'i
|
|
20
|
-
* tetiklenmez.
|
|
21
|
-
*
|
|
22
|
-
* 2. OUTPUT KATMANI (createOutputFilter):
|
|
23
|
-
* Readline'ın echo mekanizması placeholder karakterlerini terminale
|
|
24
|
-
* yazarken araya girer. Bir state machine ile karakterleri placeholder
|
|
25
|
-
* dizisiyle eşleştirir, tam eşleşme olursa terminale \n (yeni satır)
|
|
26
|
-
* yazar. Böylece kullanıcı "LINEBREAK" gibi literal placeholder
|
|
27
|
-
* yazıları görmez, satır sonlarını doğal yeni satırlar olarak deneyimler.
|
|
28
|
-
*
|
|
29
|
-
* Enter'a basıldığında, readline'dan gelen placeholder'lı satır,
|
|
30
|
-
* restoreNewlines() ile gerçek \n karakterlerine dönüştürülüp mesaj
|
|
31
|
-
* olarak işlenir.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
'use strict';
|
|
35
|
-
|
|
36
|
-
const { PassThrough } = require('stream');
|
|
37
|
-
|
|
38
|
-
const PASTE_START = '\x1b[200~';
|
|
39
|
-
const PASTE_END = '\x1b[201~';
|
|
40
|
-
|
|
41
|
-
// Input proxy ile output filter arasında paste durumunu paylaşır.
|
|
42
|
-
// escapeEmbeddedNewlines paste algıladığında bu context'i set eder,
|
|
43
|
-
// output filter tüm ekoyu yutar (terminalde hiçbir şey gözükmez),
|
|
44
|
-
// repl.js line handler'ı context'i temizler.
|
|
45
|
-
let _pasteContext = null; // { lineCount } or null
|
|
46
|
-
|
|
47
|
-
// Yapıştırılan içindeki satır sonlarını gizlemek için kullanılan placeholder.
|
|
48
|
-
// Gerçek kullanıcı girdisinde pratikte hiç geçmeyecek bir dizi.
|
|
49
|
-
const NEWLINE_PLACEHOLDER = '\u2424\u2424LINEBREAK\u2424\u2424';
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Bracketed paste'i destekleyen bir PassThrough stream döner.
|
|
53
|
-
* `source` (genellikle process.stdin) üzerinden gelen veriyi işler.
|
|
54
|
-
*
|
|
55
|
-
* readline.createInterface({ input: createPasteSafeInput(process.stdin), ... })
|
|
56
|
-
* şeklinde process.stdin yerine kullanılmalı.
|
|
57
|
-
*/
|
|
58
|
-
// str'nin sonunda PASTE_START veya PASTE_END marker'ının bir ön-eki (prefix)
|
|
59
|
-
// olarak duran kısmın uzunluğunu döner (0 = eşleşme yok). Sadece gerçekten
|
|
60
|
-
// marker olabilecek bir kuyruk varsa bekletiyoruz; aksi halde tek tuş
|
|
61
|
-
// yazımında karakterler bir sonraki tuşa kadar ekranda gecikir.
|
|
62
|
-
function trailingMarkerPrefixLen(str, marker) {
|
|
63
|
-
const maxLen = Math.min(str.length, marker.length - 1);
|
|
64
|
-
for (let len = maxLen; len > 0; len--) {
|
|
65
|
-
if (str.slice(str.length - len) === marker.slice(0, len)) return len;
|
|
66
|
-
}
|
|
67
|
-
return 0;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// TERMINAL-AGNOSTIK PASTE TESPİTİ
|
|
71
|
-
// ---------------------------------
|
|
72
|
-
// Birçok terminal (örn. macOS'un yerleşik Terminal.app'i) bracketed-paste
|
|
73
|
-
// (ESC[200~/[201~) modunu desteklemez/etkinleştirmez. Bu yüzden bracket
|
|
74
|
-
// marker'larına güvenmek yetersiz kalıyor. Daha güvenilir, terminalden
|
|
75
|
-
// bağımsız bir sinyal var: bir gerçek Enter tuşuna basış, işletim sisteminden
|
|
76
|
-
// tek başına ve tek bir karakter ('\r' veya '\n') olarak gelir — başka hiçbir
|
|
77
|
-
// karakterle birlikte değil. Ama bir yapıştırma (paste), terminal emülatörü
|
|
78
|
-
// tarafından TEK SEFERDE (çok karakterli, içinde satır sonları barındıran bir
|
|
79
|
-
// "data" event'i olarak) pty'ye yazılır. Yani: içinde satır sonu BARINDIRAN
|
|
80
|
-
// ama SADECE tek bir satır sonu karakterinden İBARET OLMAYAN bir chunk,
|
|
81
|
-
// hemen hemen kesin olarak bir paste'tir — bracket marker olsun olmasın.
|
|
82
|
-
function isLoneEnterKeystroke(str) {
|
|
83
|
-
return str === '\r' || str === '\n' || str === '\r\n';
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function escapeEmbeddedNewlines(str) {
|
|
87
|
-
if (!str || isLoneEnterKeystroke(str)) return str;
|
|
88
|
-
const newlineCount = (str.match(/\r\n|\r|\n/g) || []).length;
|
|
89
|
-
// Terminal-agnostik paste tespiti:
|
|
90
|
-
// Gerçek terminalde tuş vuruşları tek tek karakter olarak gelir.
|
|
91
|
-
// İçinde satır sonu BARINDIRAN ve sadece bir satır sonundan İBARET
|
|
92
|
-
// OLMAYAN her chunk paste'tir — kaç satır olduğu fark etmez.
|
|
93
|
-
// (Tek istisna: test ortamında PassThrough kullanılır, orada da
|
|
94
|
-
// testler karakter-karakter yazacak şekilde güncellenmiştir.)
|
|
95
|
-
if (newlineCount === 0) return str;
|
|
96
|
-
|
|
97
|
-
// Paste algılandı — output filter'a sinyal ver ve summary yaz
|
|
98
|
-
onPasteDetected(str);
|
|
99
|
-
|
|
100
|
-
return str.replace(/\r\n|\r|\n/g, NEWLINE_PLACEHOLDER);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function onPasteDetected(pasteStr) {
|
|
104
|
-
const newlineCount = (pasteStr.match(/\r\n|\r|\n/g) || []).length;
|
|
105
|
-
const lineCount = newlineCount + 1;
|
|
106
|
-
_pasteContext = { lineCount };
|
|
107
|
-
// Mevcut satırı temizle (prompt + öncesinde yazılan text) ve özet yaz
|
|
108
|
-
process.stdout.write(`\r\x1b[2K[Pasted ~${lineCount} lines]\n`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function createPasteSafeInput(source = process.stdin) {
|
|
112
|
-
const proxy = new PassThrough();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
str =
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*/
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
i
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const ch = str[i];
|
|
281
|
-
if (ch ===
|
|
282
|
-
partial
|
|
283
|
-
i++;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
},
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* paste-safe-input — Terminal'e uzun/çok satırlı metin yapıştırılınca
|
|
3
|
+
* her satırın ayrı bir "Enter" gibi gönderilip anında submit edilmesini önler.
|
|
4
|
+
*
|
|
5
|
+
* Sorun: Node'un düz `readline` arayüzü, terminalden gelen her "\n" karakterini
|
|
6
|
+
* bir 'line' event'i olarak görür. Terminal "bracketed paste mode" açık değilse
|
|
7
|
+
* (veya açık olsa da bu karakterler ayıklanmazsa), kullanıcı 10 satırlık bir metni
|
|
8
|
+
* yapıştırdığında readline bunu 10 ayrı mesaj gibi okur ve her birini hemen
|
|
9
|
+
* gönderir — kullanıcı paste'i bitirmeden cevaplar gelmeye başlar.
|
|
10
|
+
*
|
|
11
|
+
* Çözüm: İki katmanlı bir yaklaşım:
|
|
12
|
+
*
|
|
13
|
+
* 1. INPUT KATMANI (createPasteSafeInput):
|
|
14
|
+
* Terminalin "bracketed paste" (ESC[200~ ... ESC[201~) işaretleyicilerini
|
|
15
|
+
* dinleyip, bu işaretler arasındaki tüm veriyi tek parça olarak topluyoruz.
|
|
16
|
+
* Ayrıca terminal-agnostik bir heuristik: çok satırlı büyük chunk'ları da
|
|
17
|
+
* paste olarak algılar. İçindeki gerçek satır sonlarını geçici bir
|
|
18
|
+
* placeholder (NEWLINE_PLACEHOLDER) ile değiştirip readline'a TEK satır gibi
|
|
19
|
+
* besliyoruz. Kullanıcı gerçekten Enter'a basana kadar hiçbir 'line' event'i
|
|
20
|
+
* tetiklenmez.
|
|
21
|
+
*
|
|
22
|
+
* 2. OUTPUT KATMANI (createOutputFilter):
|
|
23
|
+
* Readline'ın echo mekanizması placeholder karakterlerini terminale
|
|
24
|
+
* yazarken araya girer. Bir state machine ile karakterleri placeholder
|
|
25
|
+
* dizisiyle eşleştirir, tam eşleşme olursa terminale \n (yeni satır)
|
|
26
|
+
* yazar. Böylece kullanıcı "LINEBREAK" gibi literal placeholder
|
|
27
|
+
* yazıları görmez, satır sonlarını doğal yeni satırlar olarak deneyimler.
|
|
28
|
+
*
|
|
29
|
+
* Enter'a basıldığında, readline'dan gelen placeholder'lı satır,
|
|
30
|
+
* restoreNewlines() ile gerçek \n karakterlerine dönüştürülüp mesaj
|
|
31
|
+
* olarak işlenir.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
'use strict';
|
|
35
|
+
|
|
36
|
+
const { PassThrough } = require('stream');
|
|
37
|
+
|
|
38
|
+
const PASTE_START = '\x1b[200~';
|
|
39
|
+
const PASTE_END = '\x1b[201~';
|
|
40
|
+
|
|
41
|
+
// Input proxy ile output filter arasında paste durumunu paylaşır.
|
|
42
|
+
// escapeEmbeddedNewlines paste algıladığında bu context'i set eder,
|
|
43
|
+
// output filter tüm ekoyu yutar (terminalde hiçbir şey gözükmez),
|
|
44
|
+
// repl.js line handler'ı context'i temizler.
|
|
45
|
+
let _pasteContext = null; // { lineCount } or null
|
|
46
|
+
|
|
47
|
+
// Yapıştırılan içindeki satır sonlarını gizlemek için kullanılan placeholder.
|
|
48
|
+
// Gerçek kullanıcı girdisinde pratikte hiç geçmeyecek bir dizi.
|
|
49
|
+
const NEWLINE_PLACEHOLDER = '\u2424\u2424LINEBREAK\u2424\u2424';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Bracketed paste'i destekleyen bir PassThrough stream döner.
|
|
53
|
+
* `source` (genellikle process.stdin) üzerinden gelen veriyi işler.
|
|
54
|
+
*
|
|
55
|
+
* readline.createInterface({ input: createPasteSafeInput(process.stdin), ... })
|
|
56
|
+
* şeklinde process.stdin yerine kullanılmalı.
|
|
57
|
+
*/
|
|
58
|
+
// str'nin sonunda PASTE_START veya PASTE_END marker'ının bir ön-eki (prefix)
|
|
59
|
+
// olarak duran kısmın uzunluğunu döner (0 = eşleşme yok). Sadece gerçekten
|
|
60
|
+
// marker olabilecek bir kuyruk varsa bekletiyoruz; aksi halde tek tuş
|
|
61
|
+
// yazımında karakterler bir sonraki tuşa kadar ekranda gecikir.
|
|
62
|
+
function trailingMarkerPrefixLen(str, marker) {
|
|
63
|
+
const maxLen = Math.min(str.length, marker.length - 1);
|
|
64
|
+
for (let len = maxLen; len > 0; len--) {
|
|
65
|
+
if (str.slice(str.length - len) === marker.slice(0, len)) return len;
|
|
66
|
+
}
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// TERMINAL-AGNOSTIK PASTE TESPİTİ
|
|
71
|
+
// ---------------------------------
|
|
72
|
+
// Birçok terminal (örn. macOS'un yerleşik Terminal.app'i) bracketed-paste
|
|
73
|
+
// (ESC[200~/[201~) modunu desteklemez/etkinleştirmez. Bu yüzden bracket
|
|
74
|
+
// marker'larına güvenmek yetersiz kalıyor. Daha güvenilir, terminalden
|
|
75
|
+
// bağımsız bir sinyal var: bir gerçek Enter tuşuna basış, işletim sisteminden
|
|
76
|
+
// tek başına ve tek bir karakter ('\r' veya '\n') olarak gelir — başka hiçbir
|
|
77
|
+
// karakterle birlikte değil. Ama bir yapıştırma (paste), terminal emülatörü
|
|
78
|
+
// tarafından TEK SEFERDE (çok karakterli, içinde satır sonları barındıran bir
|
|
79
|
+
// "data" event'i olarak) pty'ye yazılır. Yani: içinde satır sonu BARINDIRAN
|
|
80
|
+
// ama SADECE tek bir satır sonu karakterinden İBARET OLMAYAN bir chunk,
|
|
81
|
+
// hemen hemen kesin olarak bir paste'tir — bracket marker olsun olmasın.
|
|
82
|
+
function isLoneEnterKeystroke(str) {
|
|
83
|
+
return str === '\r' || str === '\n' || str === '\r\n';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function escapeEmbeddedNewlines(str) {
|
|
87
|
+
if (!str || isLoneEnterKeystroke(str)) return str;
|
|
88
|
+
const newlineCount = (str.match(/\r\n|\r|\n/g) || []).length;
|
|
89
|
+
// Terminal-agnostik paste tespiti:
|
|
90
|
+
// Gerçek terminalde tuş vuruşları tek tek karakter olarak gelir.
|
|
91
|
+
// İçinde satır sonu BARINDIRAN ve sadece bir satır sonundan İBARET
|
|
92
|
+
// OLMAYAN her chunk paste'tir — kaç satır olduğu fark etmez.
|
|
93
|
+
// (Tek istisna: test ortamında PassThrough kullanılır, orada da
|
|
94
|
+
// testler karakter-karakter yazacak şekilde güncellenmiştir.)
|
|
95
|
+
if (newlineCount === 0) return str;
|
|
96
|
+
|
|
97
|
+
// Paste algılandı — output filter'a sinyal ver ve summary yaz
|
|
98
|
+
onPasteDetected(str);
|
|
99
|
+
|
|
100
|
+
return str.replace(/\r\n|\r|\n/g, NEWLINE_PLACEHOLDER);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function onPasteDetected(pasteStr) {
|
|
104
|
+
const newlineCount = (pasteStr.match(/\r\n|\r|\n/g) || []).length;
|
|
105
|
+
const lineCount = newlineCount + 1;
|
|
106
|
+
_pasteContext = { lineCount };
|
|
107
|
+
// Mevcut satırı temizle (prompt + öncesinde yazılan text) ve özet yaz
|
|
108
|
+
process.stdout.write(`\r\x1b[2K[Pasted ~${lineCount} lines]\n`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function createPasteSafeInput(source = process.stdin) {
|
|
112
|
+
const proxy = new PassThrough();
|
|
113
|
+
|
|
114
|
+
// Non-TTY (pipe, script, CI) girdide paste algılama ANLAMSIZ ve zararlı:
|
|
115
|
+
// chunk'lar doğal olarak çok satırlı gelir, her girdi "paste" sanılıp
|
|
116
|
+
// satır sonları placeholder'a çevrilir ve komutlar hiç işlenmez.
|
|
117
|
+
// İnteraktif olmayan kaynakta saf geçiş yap.
|
|
118
|
+
if (!source.isTTY) {
|
|
119
|
+
source.pipe(proxy);
|
|
120
|
+
source.on('error', (e) => proxy.emit('error', e));
|
|
121
|
+
proxy.isTTY = source.isTTY;
|
|
122
|
+
proxy.setRawMode = source.setRawMode ? source.setRawMode.bind(source) : undefined;
|
|
123
|
+
proxy.ref = source.ref ? source.ref.bind(source) : undefined;
|
|
124
|
+
proxy.unref = source.unref ? source.unref.bind(source) : undefined;
|
|
125
|
+
return proxy;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let inPaste = false;
|
|
129
|
+
let carry = ''; // marker'ın chunk sınırında bölünmesine karşı tampon
|
|
130
|
+
let pasteBuffer = ''; // onPasteDetected için biriken paste içeriği
|
|
131
|
+
|
|
132
|
+
const onData = (chunk) => {
|
|
133
|
+
let str = carry + chunk.toString('utf8');
|
|
134
|
+
carry = '';
|
|
135
|
+
let out = '';
|
|
136
|
+
|
|
137
|
+
while (str.length) {
|
|
138
|
+
if (inPaste) {
|
|
139
|
+
const endIdx = str.indexOf(PASTE_END);
|
|
140
|
+
if (endIdx === -1) {
|
|
141
|
+
const keepLen = trailingMarkerPrefixLen(str, PASTE_END);
|
|
142
|
+
const flushLen = str.length - keepLen;
|
|
143
|
+
const segment = str.slice(0, flushLen);
|
|
144
|
+
pasteBuffer += segment;
|
|
145
|
+
out += segment.replace(/\r\n|\r|\n/g, NEWLINE_PLACEHOLDER);
|
|
146
|
+
carry = str.slice(flushLen);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
const pasted = str.slice(0, endIdx);
|
|
150
|
+
pasteBuffer += pasted;
|
|
151
|
+
out += pasted.replace(/\r\n|\r|\n/g, NEWLINE_PLACEHOLDER);
|
|
152
|
+
inPaste = false;
|
|
153
|
+
onPasteDetected(pasteBuffer);
|
|
154
|
+
pasteBuffer = '';
|
|
155
|
+
str = str.slice(endIdx + PASTE_END.length);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const startIdx = str.indexOf(PASTE_START);
|
|
160
|
+
if (startIdx === -1) {
|
|
161
|
+
const keepLen = trailingMarkerPrefixLen(str, PASTE_START);
|
|
162
|
+
const flushLen = str.length - keepLen;
|
|
163
|
+
// Bracket marker'ı olmayan terminallerde de paste'i yakala: bu segment
|
|
164
|
+
// tek başına bir Enter tuşu değilse ve içinde satır sonu varsa, o
|
|
165
|
+
// satır sonlarını gizle (terminal-agnostik heuristik).
|
|
166
|
+
out += escapeEmbeddedNewlines(str.slice(0, flushLen));
|
|
167
|
+
carry = str.slice(flushLen);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
out += escapeEmbeddedNewlines(str.slice(0, startIdx));
|
|
171
|
+
str = str.slice(startIdx + PASTE_START.length);
|
|
172
|
+
inPaste = true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (out) proxy.write(out);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
source.on('data', onData);
|
|
179
|
+
source.on('end', () => {
|
|
180
|
+
if (carry) {
|
|
181
|
+
proxy.write(inPaste ? carry.replace(/\r\n|\r|\n/g, NEWLINE_PLACEHOLDER) : carry);
|
|
182
|
+
carry = '';
|
|
183
|
+
}
|
|
184
|
+
proxy.end();
|
|
185
|
+
});
|
|
186
|
+
source.on('error', (e) => proxy.emit('error', e));
|
|
187
|
+
|
|
188
|
+
// process.stdin'in raw-mode/pause-resume API'lerini proxy üzerinden de eriştir,
|
|
189
|
+
// readline bunları çağırabiliyor.
|
|
190
|
+
proxy.isTTY = source.isTTY;
|
|
191
|
+
proxy.setRawMode = source.setRawMode ? source.setRawMode.bind(source) : undefined;
|
|
192
|
+
proxy.ref = source.ref ? source.ref.bind(source) : undefined;
|
|
193
|
+
proxy.unref = source.unref ? source.unref.bind(source) : undefined;
|
|
194
|
+
|
|
195
|
+
return proxy;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Terminalde bracketed paste mode'u açar. Çoğu modern terminal (iTerm2, Terminal.app,
|
|
200
|
+
* Windows Terminal, VS Code, gnome-terminal vb.) bunu destekler. Desteklemeyen
|
|
201
|
+
* terminallerde bu escape kodu sessizce yok sayılır, davranış eskisi gibi kalır.
|
|
202
|
+
*/
|
|
203
|
+
function enableBracketedPaste(out = process.stdout) {
|
|
204
|
+
if (out.isTTY) out.write('\x1b[?2004h');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function disableBracketedPaste(out = process.stdout) {
|
|
208
|
+
if (out.isTTY) out.write('\x1b[?2004l');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* readline'dan gelen satırı, paste placeholder'larını gerçek "\n" karakterine
|
|
213
|
+
* geri çevirerek normalize eder. Gönderilecek mesaj olarak bunu kullan.
|
|
214
|
+
*/
|
|
215
|
+
function restoreNewlines(line) {
|
|
216
|
+
return line.split(NEWLINE_PLACEHOLDER).join('\n');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* createOutputFilter — Readline'ın output'unda dolaşan placeholder
|
|
221
|
+
* karakterlerini (NEWLINE_PLACEHOLDER) TAMAMEN gizleyen bir wrapper.
|
|
222
|
+
* Kullanıcı terminalde ne placeholder yazılarını ne de satır sonlarını
|
|
223
|
+
* görür — paste içeriği terminal ekosunda yer kaplamaz.
|
|
224
|
+
*
|
|
225
|
+
* Paste'in içeriği terminalde görünmez; repl.js line handler'ı paste
|
|
226
|
+
* algılandığında "[Pasted ~N lines]" gibi kısa bir özet yazar.
|
|
227
|
+
*
|
|
228
|
+
* Kullanım:
|
|
229
|
+
* const outFilter = createOutputFilter(process.stdout);
|
|
230
|
+
* const rl = readline.createInterface({ input, output: outFilter, ... });
|
|
231
|
+
*
|
|
232
|
+
* Çalışma prensibi:
|
|
233
|
+
* Readline, echo mekanizmasıyla her karakteri output.write() ile
|
|
234
|
+
* terminale yazar. Placeholder karakterleri de tek tek yazılır.
|
|
235
|
+
* Bu fonksiyon bir state machine tutar: karakterleri placeholder
|
|
236
|
+
* ile eşleştirir, tam eşleşme olursa placeholder'ı ATAR (terminalde
|
|
237
|
+
* hiçbir iz kalmaz), olmazsa karakteri olduğu gibi geçirir. Kısmi
|
|
238
|
+
* eşleşmeler (chunk sınırında bölünmüş placeholder) sonraki
|
|
239
|
+
* write()'da tamamlanmak üzere partial buffer'da bekletilir.
|
|
240
|
+
*/
|
|
241
|
+
function createOutputFilter(output = process.stdout) {
|
|
242
|
+
let partial = '';
|
|
243
|
+
|
|
244
|
+
const filter = {
|
|
245
|
+
write(data) {
|
|
246
|
+
// Paste aktifken tüm ekoyu yut — terminalde hiçbir şey gözükmez
|
|
247
|
+
if (_pasteContext !== null) {
|
|
248
|
+
// Placeholder state machine'i çalıştır (sonraki write'lar için
|
|
249
|
+
// partial buffer temiz kalsın) ama output'a hiçbir şey yazma.
|
|
250
|
+
const str = data.toString('utf8');
|
|
251
|
+
let i = 0;
|
|
252
|
+
while (i < str.length) {
|
|
253
|
+
if (partial.length > 0) {
|
|
254
|
+
const expected = NEWLINE_PLACEHOLDER[partial.length];
|
|
255
|
+
const ch = str[i];
|
|
256
|
+
if (ch === expected) {
|
|
257
|
+
partial += ch;
|
|
258
|
+
i++;
|
|
259
|
+
if (partial === NEWLINE_PLACEHOLDER) partial = '';
|
|
260
|
+
} else {
|
|
261
|
+
partial = '';
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
if (str[i] === NEWLINE_PLACEHOLDER[0]) {
|
|
265
|
+
partial = str[i];
|
|
266
|
+
}
|
|
267
|
+
i++;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const str = data.toString('utf8');
|
|
274
|
+
let result = '';
|
|
275
|
+
let i = 0;
|
|
276
|
+
|
|
277
|
+
while (i < str.length) {
|
|
278
|
+
if (partial.length > 0) {
|
|
279
|
+
const expected = NEWLINE_PLACEHOLDER[partial.length];
|
|
280
|
+
const ch = str[i];
|
|
281
|
+
if (ch === expected) {
|
|
282
|
+
partial += ch;
|
|
283
|
+
i++;
|
|
284
|
+
if (partial === NEWLINE_PLACEHOLDER) {
|
|
285
|
+
partial = '';
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
result += partial;
|
|
289
|
+
partial = '';
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
const ch = str[i];
|
|
293
|
+
if (ch === NEWLINE_PLACEHOLDER[0]) {
|
|
294
|
+
partial = ch;
|
|
295
|
+
i++;
|
|
296
|
+
} else {
|
|
297
|
+
result += ch;
|
|
298
|
+
i++;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (result) return output.write(result);
|
|
304
|
+
return true;
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
end(...args) {
|
|
308
|
+
if (partial) {
|
|
309
|
+
output.write(partial);
|
|
310
|
+
partial = '';
|
|
311
|
+
}
|
|
312
|
+
return output.end(...args);
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
get columns() { return output.columns; },
|
|
316
|
+
get rows() { return output.rows; },
|
|
317
|
+
get isTTY() { return output.isTTY; },
|
|
318
|
+
|
|
319
|
+
on(...args) { return output.on(...args); },
|
|
320
|
+
off(...args) { return output.off ? output.off(...args) : undefined; },
|
|
321
|
+
addListener(...args) { return output.addListener(...args); },
|
|
322
|
+
removeListener(...args) { return output.removeListener(...args); },
|
|
323
|
+
emit(...args) { return output.emit(...args); },
|
|
324
|
+
getColorDepth(...args) {
|
|
325
|
+
return typeof output.getColorDepth === 'function'
|
|
326
|
+
? output.getColorDepth(...args)
|
|
327
|
+
: undefined;
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
return filter;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function setPasteContext(ctx) { _pasteContext = ctx; }
|
|
335
|
+
function clearPasteContext() { _pasteContext = null; }
|
|
336
|
+
|
|
337
|
+
module.exports = {
|
|
338
|
+
createPasteSafeInput,
|
|
339
|
+
createOutputFilter,
|
|
340
|
+
enableBracketedPaste,
|
|
341
|
+
disableBracketedPaste,
|
|
342
|
+
restoreNewlines,
|
|
343
|
+
NEWLINE_PLACEHOLDER,
|
|
344
|
+
setPasteContext,
|
|
345
|
+
clearPasteContext,
|
|
346
|
+
};
|