@nibgat/aide 0.0.0-stage → 0.2.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 +79 -2
- package/aide.mjs +205 -0
- package/package.json +29 -4
- package/src/client.mjs +260 -0
- package/src/commands.mjs +360 -0
- package/src/device.mjs +488 -0
- package/src/repl.mjs +501 -0
- package/src/ui.mjs +260 -0
package/src/ui.mjs
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal arayüzü: renkler, adım göstergesi (spinner), terminalde Markdown ve tablo/çıktı biçimleme. Bağımlılıksız.
|
|
3
|
+
*/
|
|
4
|
+
const isTTY = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR;
|
|
5
|
+
const paint = (open, close = 0) => (text) => isTTY ? `\x1b[${open}m${text}\x1b[${close}m` : String(text);
|
|
6
|
+
|
|
7
|
+
const c = {
|
|
8
|
+
italic: paint(3, 23),
|
|
9
|
+
violet: paint(35, 39),
|
|
10
|
+
green: paint(32, 39),
|
|
11
|
+
cyan: paint(36, 39),
|
|
12
|
+
gray: paint(90, 39),
|
|
13
|
+
bold: paint(1, 22),
|
|
14
|
+
red: paint(31, 39),
|
|
15
|
+
yellow: paint(33, 39),
|
|
16
|
+
dim: paint(2, 22),
|
|
17
|
+
accent: (text) => isTTY ? `\x1b[38;5;177m${text}\x1b[39m` : String(text)
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const ANSI = /\x1b\[[0-9;]*m/g;
|
|
21
|
+
const visibleLength = (text) => String(text).replace(ANSI, '').length;
|
|
22
|
+
const columns = () => process.stdout.columns || 100;
|
|
23
|
+
|
|
24
|
+
// ─────────────────────────────── adım göstergesi ───────────────────────────────
|
|
25
|
+
|
|
26
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
27
|
+
|
|
28
|
+
/** stderr'de tek satırlık canlı gösterge: "⠋ Filo → data_query (12 sn · Ctrl+C iptal)". */
|
|
29
|
+
const createSpinner = (label = 'Düşünüyor') => {
|
|
30
|
+
const started = Date.now();
|
|
31
|
+
let text = label;
|
|
32
|
+
let frame = 0;
|
|
33
|
+
let timer = null;
|
|
34
|
+
const render = () => {
|
|
35
|
+
if(!process.stderr.isTTY) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const seconds = Math.round((Date.now() - started) / 1000);
|
|
40
|
+
const line = `${c.accent(FRAMES[frame % FRAMES.length])} ${text} ${c.dim(`(${seconds} sn · Ctrl+C iptal)`)}`;
|
|
41
|
+
|
|
42
|
+
process.stderr.write(`\r\x1b[2K${line.length > columns() + 40 ? line.slice(0, columns() + 30) : line}`);
|
|
43
|
+
frame += 1;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
start() {
|
|
48
|
+
if(!timer) {
|
|
49
|
+
render();
|
|
50
|
+
timer = setInterval(render, 90);
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
update(next) {
|
|
54
|
+
text = next;
|
|
55
|
+
render();
|
|
56
|
+
},
|
|
57
|
+
/** Göstergeyi siler; `done` verilirse yerine kalıcı soluk bir adım satırı bırakır. */
|
|
58
|
+
stop(done) {
|
|
59
|
+
clearInterval(timer);
|
|
60
|
+
timer = null;
|
|
61
|
+
|
|
62
|
+
if(process.stderr.isTTY) {
|
|
63
|
+
process.stderr.write('\r\x1b[2K');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if(done) {
|
|
67
|
+
process.stderr.write(`${c.dim(` ${done}`)}\n`);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
elapsed: () => Date.now() - started
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ─────────────────────────────── Markdown ───────────────────────────────
|
|
75
|
+
|
|
76
|
+
const inline = (text) => String(text)
|
|
77
|
+
.replace(/`([^`]+)`/g, (match, code) => c.cyan(code))
|
|
78
|
+
.replace(/\*\*([^*]+)\*\*/g, (match, bold) => c.bold(bold))
|
|
79
|
+
.replace(/(^|[\s(])\*([^*\s][^*]*)\*(?=[\s).,!?:;]|$)/g, (match, lead, italic) => `${lead}${c.italic(italic)}`)
|
|
80
|
+
.replace(/(^|[\s(])_([^_\s][^_]*)_(?=[\s).,!?:;]|$)/g, (match, lead, italic) => `${lead}${c.italic(italic)}`)
|
|
81
|
+
.replace(/\[([^\]]+)\]\((https?:[^)]+)\)/g, (match, label, url) => `${label} ${c.dim(`(${url})`)}`);
|
|
82
|
+
|
|
83
|
+
const renderTable = (lines) => {
|
|
84
|
+
const rows = lines.filter((line) => !/^\s*\|?\s*:?-{2,}/.test(line)).map((line) => line.trim().replace(/^\||\|$/g, '').split('|').map((cell) => inline(cell.trim())));
|
|
85
|
+
const count = Math.max(...rows.map((row) => row.length));
|
|
86
|
+
const widths = Array.from({ length: count }, (value, index) => Math.min(40, Math.max(...rows.map((row) => visibleLength(row[index] || '')))));
|
|
87
|
+
const pad = (cell = '', width) => {
|
|
88
|
+
const plain = visibleLength(cell);
|
|
89
|
+
|
|
90
|
+
return plain > width ? `${cell.replace(ANSI, '').slice(0, width - 1)}…` : `${cell}${' '.repeat(width - plain)}`;
|
|
91
|
+
};
|
|
92
|
+
const rule = (left, middle, right) => c.gray(`${left}${widths.map((width) => '─'.repeat(width + 2)).join(middle)}${right}`);
|
|
93
|
+
const out = [rule('┌', '┬', '┐')];
|
|
94
|
+
|
|
95
|
+
rows.forEach((row, index) => {
|
|
96
|
+
out.push(`${c.gray('│')} ${widths.map((width, column) => index === 0 ? c.bold(pad(row[column], width)) : pad(row[column], width)).join(` ${c.gray('│')} `)} ${c.gray('│')}`);
|
|
97
|
+
|
|
98
|
+
if(index === 0 && rows.length > 1) {
|
|
99
|
+
out.push(rule('├', '┼', '┤'));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
out.push(rule('└', '┴', '┘'));
|
|
103
|
+
|
|
104
|
+
return out;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Sohbet yanıtlarındaki Markdown'ı terminale çevirir: başlık, liste, alıntı, kod bloğu, tablo, kalın/eğik/kod/bağlantı. */
|
|
108
|
+
const renderMarkdown = (markdown) => {
|
|
109
|
+
const lines = String(markdown || '').replace(/\r/g, '').split('\n');
|
|
110
|
+
const out = [];
|
|
111
|
+
let inCode = false;
|
|
112
|
+
let table = [];
|
|
113
|
+
const flushTable = () => {
|
|
114
|
+
if(table.length > 0) {
|
|
115
|
+
out.push(...renderTable(table));
|
|
116
|
+
table = [];
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
for(const line of lines) {
|
|
121
|
+
if(/^\s*```/.test(line)) {
|
|
122
|
+
flushTable();
|
|
123
|
+
inCode = !inCode;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if(inCode) {
|
|
128
|
+
out.push(`${c.gray('│')} ${c.cyan(line)}`);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if(/^\s*\|.*\|\s*$/.test(line)) {
|
|
133
|
+
table.push(line);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
flushTable();
|
|
138
|
+
|
|
139
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
140
|
+
|
|
141
|
+
if(heading) {
|
|
142
|
+
out.push(c.bold(c.accent(inline(heading[2]))));
|
|
143
|
+
} else if(/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
|
|
144
|
+
out.push(c.gray('─'.repeat(Math.min(60, columns()))));
|
|
145
|
+
} else if(/^\s*>\s?/.test(line)) {
|
|
146
|
+
out.push(`${c.gray('▌')} ${c.dim(inline(line.replace(/^\s*>\s?/, '')))}`);
|
|
147
|
+
} else if(/^(\s*)[-*+]\s+/.test(line)) {
|
|
148
|
+
out.push(line.replace(/^(\s*)[-*+]\s+(.*)$/, (match, indent, rest) => `${indent}${c.accent('•')} ${inline(rest)}`));
|
|
149
|
+
} else if(/^(\s*)\d+[.)]\s+/.test(line)) {
|
|
150
|
+
out.push(line.replace(/^(\s*)(\d+[.)])\s+(.*)$/, (match, indent, number, rest) => `${indent}${c.accent(number)} ${inline(rest)}`));
|
|
151
|
+
} else {
|
|
152
|
+
out.push(inline(line));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
flushTable();
|
|
157
|
+
|
|
158
|
+
return out.join('\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// ─────────────────────────────── tablo ve çıktı ───────────────────────────────
|
|
162
|
+
|
|
163
|
+
const short = (value, width = 48) => {
|
|
164
|
+
const text = value === null || value === undefined ? '—' : typeof value === 'object' ? JSON.stringify(value) : String(value);
|
|
165
|
+
const flat = text.replace(/\s+/g, ' ');
|
|
166
|
+
|
|
167
|
+
return flat.length > width ? `${flat.slice(0, width - 1)}…` : flat;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const PREFERRED = ['id', 'name', 'title', 'role', 'department', 'status', 'email', 'driver', 'enabled', 'kind', 'messageCount', 'updatedAt', 'createdAt'];
|
|
171
|
+
|
|
172
|
+
const table = (rows, keys) => {
|
|
173
|
+
const fields = keys || (() => {
|
|
174
|
+
const all = [...new Set(rows.flatMap((row) => Object.keys(row || {})))].filter((key) => rows.some((row) => row?.[key] === null || typeof row?.[key] !== 'object'));
|
|
175
|
+
|
|
176
|
+
return [...PREFERRED.filter((key) => all.includes(key)), ...all.filter((key) => !PREFERRED.includes(key))].slice(0, 5);
|
|
177
|
+
})();
|
|
178
|
+
const cells = rows.map((row) => fields.map((key) => short(row?.[key], key === 'id' ? 40 : 36)));
|
|
179
|
+
const widths = fields.map((key, index) => Math.max(key.length, ...cells.map((cell) => cell[index].length)));
|
|
180
|
+
|
|
181
|
+
console.log(c.bold(fields.map((key, index) => key.padEnd(widths[index])).join(' ')));
|
|
182
|
+
cells.forEach((cell) => console.log(cell.map((value, index) => value.padEnd(widths[index])).join(' ')));
|
|
183
|
+
console.log(c.dim(`${rows.length} kayıt`));
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/** Yanıtın asıl verisi: { data } / { items } / { sessions } … tek dizi ya da nesne. */
|
|
187
|
+
const unwrap = (json) => {
|
|
188
|
+
if(Array.isArray(json)) {
|
|
189
|
+
return json;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for(const key of ['data', 'items', 'employees', 'sessions', 'notes', 'rules', 'schedules', 'members', 'sources', 'skillSets', 'notifications', 'messages', 'entries', 'reminders', 'projects', 'questions', 'project', 'question', 'reminder', 'prefs', 'result']) {
|
|
193
|
+
if(json && json[key] !== undefined) {
|
|
194
|
+
return json[key];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const { success, ...rest } = json || {};
|
|
199
|
+
|
|
200
|
+
return rest;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const print = (json, flags = {}, spec = {}) => {
|
|
204
|
+
if(flags.json) {
|
|
205
|
+
console.log(JSON.stringify(json, null, 2));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const value = unwrap(json);
|
|
210
|
+
|
|
211
|
+
if(Array.isArray(value)) {
|
|
212
|
+
if(value.length === 0) {
|
|
213
|
+
console.log(c.dim('(boş)'));
|
|
214
|
+
} else if(value.every((item) => item && typeof item === 'object')) {
|
|
215
|
+
table(value, flags.columns ? String(flags.columns).split(',') : spec.columns);
|
|
216
|
+
} else {
|
|
217
|
+
value.forEach((item) => console.log(`${c.accent('•')} ${item}`));
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if(value && typeof value === 'object') {
|
|
223
|
+
const entries = Object.entries(value);
|
|
224
|
+
|
|
225
|
+
if(entries.length === 0) {
|
|
226
|
+
console.log(c.green('✓ Tamam.'));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const width = Math.min(24, Math.max(...entries.map(([key]) => key.length)));
|
|
231
|
+
|
|
232
|
+
entries.forEach(([key, item]) => console.log(`${c.bold(key.padEnd(width))} ${item !== null && typeof item === 'object' ? c.dim(short(item, 100)) : short(item, 100)}`));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log(value ?? c.green('✓ Tamam.'));
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/** Çerçeveli kutu (karşılama ekranı). */
|
|
240
|
+
const box = (lines) => {
|
|
241
|
+
const width = Math.min(columns() - 2, Math.max(...lines.map(visibleLength)) + 4);
|
|
242
|
+
const top = c.accent(`╭${'─'.repeat(width - 2)}╮`);
|
|
243
|
+
const bottom = c.accent(`╰${'─'.repeat(width - 2)}╯`);
|
|
244
|
+
const body = lines.map((line) => `${c.accent('│')} ${line}${' '.repeat(Math.max(0, width - 4 - visibleLength(line)))} ${c.accent('│')}`);
|
|
245
|
+
|
|
246
|
+
return [top, ...body, bottom].join('\n');
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export {
|
|
250
|
+
renderMarkdown,
|
|
251
|
+
createSpinner,
|
|
252
|
+
visibleLength,
|
|
253
|
+
columns,
|
|
254
|
+
unwrap,
|
|
255
|
+
short,
|
|
256
|
+
print,
|
|
257
|
+
table,
|
|
258
|
+
box,
|
|
259
|
+
c
|
|
260
|
+
};
|