geeto 0.4.3 → 0.6.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 +1 -1
- package/lib/api/copilot.d.ts +3 -3
- package/lib/api/copilot.js +3 -3
- package/lib/cli/input.d.ts +21 -18
- package/lib/cli/input.d.ts.map +1 -1
- package/lib/cli/input.js +174 -372
- package/lib/cli/input.js.map +1 -1
- package/lib/cli/menu.d.ts.map +1 -1
- package/lib/cli/menu.js +83 -4
- package/lib/cli/menu.js.map +1 -1
- package/lib/core/copilot-setup.d.ts +2 -2
- package/lib/core/copilot-setup.d.ts.map +1 -1
- package/lib/core/copilot-setup.js +16 -19
- package/lib/core/copilot-setup.js.map +1 -1
- package/lib/core/setup.d.ts +1 -1
- package/lib/core/setup.js +1 -1
- package/lib/index.js +203 -3
- package/lib/index.js.map +1 -1
- package/lib/utils/branch-naming.d.ts.map +1 -1
- package/lib/utils/branch-naming.js +17 -7
- package/lib/utils/branch-naming.js.map +1 -1
- package/lib/utils/dry-run.d.ts +21 -0
- package/lib/utils/dry-run.d.ts.map +1 -0
- package/lib/utils/dry-run.js +102 -0
- package/lib/utils/dry-run.js.map +1 -0
- package/lib/utils/exec.d.ts.map +1 -1
- package/lib/utils/exec.js +13 -0
- package/lib/utils/exec.js.map +1 -1
- package/lib/utils/git-ai.d.ts.map +1 -1
- package/lib/utils/git-ai.js +40 -21
- package/lib/utils/git-ai.js.map +1 -1
- package/lib/utils/git-errors.d.ts.map +1 -1
- package/lib/utils/git-errors.js +15 -2
- package/lib/utils/git-errors.js.map +1 -1
- package/lib/utils/menu-builders.js +1 -1
- package/lib/utils/menu-builders.js.map +1 -1
- package/lib/utils/scramble.d.ts +117 -0
- package/lib/utils/scramble.d.ts.map +1 -0
- package/lib/utils/scramble.js +317 -0
- package/lib/utils/scramble.js.map +1 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/workflows/abort.d.ts +9 -0
- package/lib/workflows/abort.d.ts.map +1 -0
- package/lib/workflows/abort.js +158 -0
- package/lib/workflows/abort.js.map +1 -0
- package/lib/workflows/ai-provider.js +1 -1
- package/lib/workflows/ai-provider.js.map +1 -1
- package/lib/workflows/alias.d.ts +6 -0
- package/lib/workflows/alias.d.ts.map +1 -0
- package/lib/workflows/alias.js +420 -0
- package/lib/workflows/alias.js.map +1 -0
- package/lib/workflows/amend.d.ts.map +1 -1
- package/lib/workflows/amend.js +9 -4
- package/lib/workflows/amend.js.map +1 -1
- package/lib/workflows/branch-helpers.js +2 -2
- package/lib/workflows/branch-helpers.js.map +1 -1
- package/lib/workflows/branch-utils.d.ts.map +1 -1
- package/lib/workflows/branch-utils.js +4 -3
- package/lib/workflows/branch-utils.js.map +1 -1
- package/lib/workflows/branch.d.ts.map +1 -1
- package/lib/workflows/branch.js +7 -4
- package/lib/workflows/branch.js.map +1 -1
- package/lib/workflows/cleanup.js +3 -3
- package/lib/workflows/cleanup.js.map +1 -1
- package/lib/workflows/commit.d.ts.map +1 -1
- package/lib/workflows/commit.js +58 -6
- package/lib/workflows/commit.js.map +1 -1
- package/lib/workflows/dry-run.d.ts +5 -0
- package/lib/workflows/dry-run.d.ts.map +1 -0
- package/lib/workflows/dry-run.js +127 -0
- package/lib/workflows/dry-run.js.map +1 -0
- package/lib/workflows/fetch.d.ts +9 -0
- package/lib/workflows/fetch.d.ts.map +1 -0
- package/lib/workflows/fetch.js +118 -0
- package/lib/workflows/fetch.js.map +1 -0
- package/lib/workflows/issue.d.ts.map +1 -1
- package/lib/workflows/issue.js +317 -72
- package/lib/workflows/issue.js.map +1 -1
- package/lib/workflows/main-steps.d.ts.map +1 -1
- package/lib/workflows/main-steps.js +144 -99
- package/lib/workflows/main-steps.js.map +1 -1
- package/lib/workflows/main.d.ts.map +1 -1
- package/lib/workflows/main.js +14 -6
- package/lib/workflows/main.js.map +1 -1
- package/lib/workflows/pr.d.ts.map +1 -1
- package/lib/workflows/pr.js +307 -39
- package/lib/workflows/pr.js.map +1 -1
- package/lib/workflows/prune.d.ts +9 -0
- package/lib/workflows/prune.d.ts.map +1 -0
- package/lib/workflows/prune.js +116 -0
- package/lib/workflows/prune.js.map +1 -0
- package/lib/workflows/pull.d.ts +9 -0
- package/lib/workflows/pull.d.ts.map +1 -0
- package/lib/workflows/pull.js +281 -0
- package/lib/workflows/pull.js.map +1 -0
- package/lib/workflows/release.d.ts.map +1 -1
- package/lib/workflows/release.js +50 -38
- package/lib/workflows/release.js.map +1 -1
- package/lib/workflows/repo-settings.js +2 -2
- package/lib/workflows/repo-settings.js.map +1 -1
- package/lib/workflows/revert.d.ts +9 -0
- package/lib/workflows/revert.d.ts.map +1 -0
- package/lib/workflows/revert.js +77 -0
- package/lib/workflows/revert.js.map +1 -0
- package/lib/workflows/reword.d.ts +9 -0
- package/lib/workflows/reword.d.ts.map +1 -0
- package/lib/workflows/reword.js +722 -0
- package/lib/workflows/reword.js.map +1 -0
- package/lib/workflows/settings.js +1 -1
- package/lib/workflows/settings.js.map +1 -1
- package/lib/workflows/status.d.ts +9 -0
- package/lib/workflows/status.d.ts.map +1 -0
- package/lib/workflows/status.js +164 -0
- package/lib/workflows/status.js.map +1 -0
- package/package.json +1 -1
package/lib/cli/input.js
CHANGED
|
@@ -21,7 +21,7 @@ export const askQuestion = (question, defaultValue) => {
|
|
|
21
21
|
process.stdout.write('\u001B[?25h');
|
|
22
22
|
const platform = os.platform();
|
|
23
23
|
const fullQuestion = defaultValue ? `${question} (${defaultValue}) ` : question;
|
|
24
|
-
process.stdout.write(fullQuestion);
|
|
24
|
+
process.stdout.write(`\n${fullQuestion}`);
|
|
25
25
|
const result = platform === 'win32'
|
|
26
26
|
? spawnSync('powershell', ['-Command', '$input = Read-Host; Write-Output $input'], {
|
|
27
27
|
stdio: ['inherit', 'pipe', 'inherit'],
|
|
@@ -34,38 +34,6 @@ export const askQuestion = (question, defaultValue) => {
|
|
|
34
34
|
const input = result.stdout?.trim() ?? '';
|
|
35
35
|
return input ?? defaultValue ?? '';
|
|
36
36
|
};
|
|
37
|
-
/**
|
|
38
|
-
* Progress bar utility for long operations
|
|
39
|
-
*/
|
|
40
|
-
export class ProgressBar {
|
|
41
|
-
constructor(total, title = 'Progress', width = 40) {
|
|
42
|
-
this.total = total;
|
|
43
|
-
this.current = 0;
|
|
44
|
-
this.width = width;
|
|
45
|
-
this.title = title;
|
|
46
|
-
}
|
|
47
|
-
update(current) {
|
|
48
|
-
this.current = Math.min(current, this.total);
|
|
49
|
-
this.render();
|
|
50
|
-
}
|
|
51
|
-
increment(amount = 1) {
|
|
52
|
-
this.current = Math.min(this.current + amount, this.total);
|
|
53
|
-
this.render();
|
|
54
|
-
}
|
|
55
|
-
complete() {
|
|
56
|
-
this.current = this.total;
|
|
57
|
-
this.render();
|
|
58
|
-
console.log(''); // New line after completion
|
|
59
|
-
}
|
|
60
|
-
render() {
|
|
61
|
-
const percentage = this.total > 0 ? Math.round((this.current / this.total) * 100) : 0;
|
|
62
|
-
const filled = Math.round((this.current / this.total) * this.width);
|
|
63
|
-
const empty = this.width - filled;
|
|
64
|
-
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
65
|
-
const status = `${this.current}/${this.total} (${percentage}%)`;
|
|
66
|
-
process.stdout.write(`\r${this.title}: [${bar}] ${status}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
37
|
/**
|
|
70
38
|
* Syntax highlighting for git diff output
|
|
71
39
|
*/
|
|
@@ -88,377 +56,211 @@ export const confirm = (question, defaultYes = true) => {
|
|
|
88
56
|
}
|
|
89
57
|
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
90
58
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const str = { re: /(['"`])(?:(?!\1).)*\1/g, c: g };
|
|
109
|
-
const num = { re: /\b\d+\.?\d*\b/g, c: m };
|
|
110
|
-
if (/^\.(sh|bash|bashrc|zshrc|zsh|profile|bash_profile)$/.test(ext)) {
|
|
111
|
-
return [{ re: /#.*/g, c: gr }, str, { re: /\$\{?\w+\}?/g, c: y }, { re: SH_RE, c }];
|
|
112
|
-
}
|
|
113
|
-
if (/^\.(js|ts|jsx|tsx|mjs|cjs)$/.test(ext)) {
|
|
114
|
-
return [{ re: /\/\/.*/g, c: gr }, str, { re: JS_RE, c }, num];
|
|
115
|
-
}
|
|
116
|
-
if (ext === '.py') {
|
|
117
|
-
return [{ re: /#.*/g, c: gr }, str, { re: PY_RE, c }, num];
|
|
118
|
-
}
|
|
119
|
-
if (ext === '.json') {
|
|
120
|
-
return [str, num, { re: /\b(true|false|null)\b/g, c }];
|
|
121
|
-
}
|
|
122
|
-
if (ext === '.md') {
|
|
123
|
-
return [
|
|
124
|
-
{ re: /^#{1,6}\s.*/g, c },
|
|
125
|
-
{ re: /\*\*[^*]+\*\*/g, c: y },
|
|
126
|
-
{ re: /`[^`]+`/g, c: g },
|
|
127
|
-
];
|
|
128
|
-
}
|
|
129
|
-
if (/^\.(ya?ml|toml)$/.test(ext)) {
|
|
130
|
-
return [
|
|
131
|
-
{ re: /#.*/g, c: gr },
|
|
132
|
-
str,
|
|
133
|
-
{ re: /^[\w.-]+(?=\s*[=:])/gm, c },
|
|
134
|
-
{ re: /\b(true|false)\b/g, c: m },
|
|
135
|
-
num,
|
|
136
|
-
];
|
|
59
|
+
/**
|
|
60
|
+
* Interactive multiline text input with editing support.
|
|
61
|
+
*
|
|
62
|
+
* Shortcuts:
|
|
63
|
+
* - Enter → new line
|
|
64
|
+
* - Backspace → delete character
|
|
65
|
+
* - Ctrl+W → delete word
|
|
66
|
+
* - Ctrl+U → clear current line
|
|
67
|
+
* - Ctrl+L → clear all text
|
|
68
|
+
* - Ctrl+D → submit
|
|
69
|
+
* - Ctrl+C → cancel
|
|
70
|
+
*
|
|
71
|
+
* Returns trimmed text or `null` when cancelled.
|
|
72
|
+
*/
|
|
73
|
+
export const askMultiline = (question, initialText = '') => {
|
|
74
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
75
|
+
process.stdin.setRawMode(false);
|
|
137
76
|
}
|
|
138
|
-
|
|
139
|
-
|
|
77
|
+
process.stdout.write('\u001B[?25h');
|
|
78
|
+
const isMac = process.platform === 'darwin';
|
|
79
|
+
const delWordHint = isMac ? '⌥⌫' : 'Ctrl+W';
|
|
80
|
+
const showHeader = () => {
|
|
81
|
+
console.log(`\n\u001B[36m?\u001B[0m ${question}`);
|
|
82
|
+
console.log(` \u001B[90mEnter=newline | Ctrl+D=submit | Ctrl+C=cancel\u001B[0m`);
|
|
83
|
+
console.log(` \u001B[90m${delWordHint}=del word | Ctrl+U=del line | Ctrl+L=clear all\u001B[0m`);
|
|
84
|
+
};
|
|
85
|
+
showHeader();
|
|
86
|
+
if (initialText.trim()) {
|
|
87
|
+
console.log(' \u001B[90m── current ──\u001B[0m');
|
|
88
|
+
for (const l of initialText.split('\n')) {
|
|
89
|
+
console.log(` \u001B[90m${l}\u001B[0m`);
|
|
90
|
+
}
|
|
91
|
+
console.log(' \u001B[90m── type below (Ctrl+D empty = keep) ──\u001B[0m');
|
|
140
92
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
93
|
+
// Non-TTY fallback (piped input)
|
|
94
|
+
if (!process.stdin.isTTY) {
|
|
95
|
+
const r = spawnSync('cat', [], {
|
|
96
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
97
|
+
encoding: 'utf8',
|
|
98
|
+
});
|
|
99
|
+
const t = r.stdout?.trim() ?? '';
|
|
100
|
+
if (!t && initialText.trim())
|
|
101
|
+
return initialText.trim();
|
|
102
|
+
return t || null;
|
|
150
103
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// which means bare \n won't carriage-return
|
|
166
|
-
const NL = '\r\n';
|
|
167
|
-
/* ── helpers ────────────────────────────────────── */
|
|
168
|
-
const clamp = (v, lo, hi) => Math.min(Math.max(v, lo), hi);
|
|
169
|
-
const lineAt = (r) => lines[r] ?? '';
|
|
170
|
-
const render = () => {
|
|
171
|
-
let frame = '';
|
|
172
|
-
if (rendered) {
|
|
173
|
-
// Go up one line at a time (most compatible) and clear each
|
|
174
|
-
for (let i = 0; i < totalLines; i++) {
|
|
175
|
-
frame += '\u001B[A'; // CUU — cursor up 1
|
|
176
|
-
}
|
|
177
|
-
frame += '\r'; // ensure column 0
|
|
178
|
-
frame += '\u001B[0J'; // ED 0 — clear from cursor to end of screen
|
|
179
|
-
}
|
|
180
|
-
rendered = true;
|
|
181
|
-
// Content lines — reserve 1 extra col for cursor at end of line
|
|
182
|
-
const scrollTop = Math.max(0, row - maxRows + 1);
|
|
183
|
-
for (let i = 0; i < maxRows; i++) {
|
|
184
|
-
const idx = scrollTop + i;
|
|
185
|
-
if (idx < lines.length) {
|
|
186
|
-
const num = String(idx + 1).padStart(3);
|
|
187
|
-
const line = lines[idx] ?? '';
|
|
188
|
-
const maxLen = cols - 9;
|
|
189
|
-
const visible = line.length > maxLen ? line.slice(0, maxLen - 1) + '…' : line;
|
|
190
|
-
if (idx === row) {
|
|
191
|
-
const c = clamp(col, 0, visible.length);
|
|
192
|
-
const before = colorize(visible.slice(0, c), synRules);
|
|
193
|
-
const cursor = visible[c] ?? ' ';
|
|
194
|
-
const after = colorize(visible.slice(c + 1), synRules);
|
|
195
|
-
frame += ` \u001B[90m${num}\u001B[0m \u001B[36m│\u001B[0m ${before}\u001B[7m${cursor}\u001B[27m${after}${NL}`;
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
const hl = colorize(visible, synRules);
|
|
199
|
-
frame += ` \u001B[90m${num}\u001B[0m \u001B[90m│\u001B[0m ${hl}${NL}`;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
frame += ` \u001B[90m │ ~\u001B[0m${NL}`;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
// Footer — title + hints + cursor position
|
|
207
|
-
frame += ` \u001B[36m─── ${label} ───\u001B[0m \u001B[90mCtrl+S save · Esc cancel · Ctrl+K del line · ⌥←→ word · Ln ${row + 1}/${lines.length} · Col ${col + 1}\u001B[0m${NL}`;
|
|
208
|
-
process.stdout.write(frame);
|
|
209
|
-
};
|
|
210
|
-
/* ── raw-mode input handling ───────────────────── */
|
|
211
|
-
if (process.stdin.isTTY) {
|
|
212
|
-
process.stdin.setRawMode(true);
|
|
104
|
+
// Pause readline so fs.readSync can use fd 0
|
|
105
|
+
rl.pause();
|
|
106
|
+
process.stdin.setRawMode(true);
|
|
107
|
+
const lines = [''];
|
|
108
|
+
let li = 0;
|
|
109
|
+
const buf = Buffer.alloc(16);
|
|
110
|
+
/** Redraw all text from scratch (clears screen) */
|
|
111
|
+
const fullRedraw = () => {
|
|
112
|
+
process.stdout.write('\u001B[2J\u001B[H');
|
|
113
|
+
showHeader();
|
|
114
|
+
for (let i = 0; i <= li; i++) {
|
|
115
|
+
process.stdout.write(lines[i] ?? '');
|
|
116
|
+
if (i < li)
|
|
117
|
+
process.stdout.write('\n');
|
|
213
118
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
};
|
|
231
|
-
const onData = (buf) => {
|
|
232
|
-
const raw = buf.toString('utf8');
|
|
233
|
-
// If we have a pending escape, accumulate
|
|
234
|
-
if (escBuf) {
|
|
235
|
-
escBuf += raw;
|
|
236
|
-
if (escTimer)
|
|
237
|
-
clearTimeout(escTimer);
|
|
238
|
-
}
|
|
239
|
-
else if (raw === '\u001B') {
|
|
240
|
-
// Start escape sequence
|
|
241
|
-
escBuf = '\u001B';
|
|
242
|
-
escTimer = setTimeout(() => {
|
|
243
|
-
// Standalone Escape — cancel editing
|
|
244
|
-
escBuf = '';
|
|
245
|
-
cleanup();
|
|
246
|
-
resolve(null);
|
|
247
|
-
}, 50);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
const key = escBuf || raw;
|
|
251
|
-
escBuf = '';
|
|
252
|
-
if (escTimer) {
|
|
253
|
-
clearTimeout(escTimer);
|
|
254
|
-
escTimer = null;
|
|
255
|
-
}
|
|
256
|
-
// Ctrl+S (0x13) — save
|
|
257
|
-
if (key === '\u0013') {
|
|
258
|
-
cleanup();
|
|
259
|
-
resolve(lines.join('\n').trim());
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
// Ctrl+C (0x03) — cancel
|
|
263
|
-
if (key === '\u0003') {
|
|
264
|
-
cleanup();
|
|
265
|
-
resolve(null);
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
// Ctrl+K (0x0B) — delete current line
|
|
269
|
-
if (key === '\u000B') {
|
|
270
|
-
if (lines.length > 1) {
|
|
271
|
-
lines.splice(row, 1);
|
|
272
|
-
if (row >= lines.length)
|
|
273
|
-
row = lines.length - 1;
|
|
274
|
-
col = clamp(col, 0, lineAt(row).length);
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
lines[0] = '';
|
|
278
|
-
col = 0;
|
|
279
|
-
}
|
|
280
|
-
render();
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
// Arrow up
|
|
284
|
-
if (key === '\u001B[A') {
|
|
285
|
-
if (row > 0) {
|
|
286
|
-
row--;
|
|
287
|
-
col = clamp(col, 0, lineAt(row).length);
|
|
288
|
-
}
|
|
289
|
-
render();
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
// Arrow down
|
|
293
|
-
if (key === '\u001B[B') {
|
|
294
|
-
if (row < lines.length - 1) {
|
|
295
|
-
row++;
|
|
296
|
-
col = clamp(col, 0, lineAt(row).length);
|
|
297
|
-
}
|
|
298
|
-
render();
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
// Cmd+Right / Option+Right / Ctrl+Right / Arrow right
|
|
302
|
-
// macOS: Cmd+Right → \x1B[1;9C → end of line
|
|
303
|
-
// Option+Right → \x1Bf or \x1B[1;3C → forward word
|
|
304
|
-
// Linux/Win: Ctrl+Right → \x1B[1;5C → forward word
|
|
305
|
-
if (key === '\u001B[1;9C') {
|
|
306
|
-
col = lineAt(row).length;
|
|
307
|
-
render();
|
|
308
|
-
return;
|
|
119
|
+
};
|
|
120
|
+
/** Delete word backwards on current line */
|
|
121
|
+
const deleteWord = () => {
|
|
122
|
+
const cur = lines[li] ?? '';
|
|
123
|
+
if (!cur)
|
|
124
|
+
return;
|
|
125
|
+
const stripped = cur.replace(/\s+$/, '');
|
|
126
|
+
const sp = stripped.lastIndexOf(' ');
|
|
127
|
+
lines[li] = sp === -1 ? '' : stripped.slice(0, sp + 1);
|
|
128
|
+
process.stdout.write(`\r\u001B[K${lines[li]}`);
|
|
129
|
+
};
|
|
130
|
+
try {
|
|
131
|
+
for (;;) {
|
|
132
|
+
let n;
|
|
133
|
+
try {
|
|
134
|
+
n = fs.readSync(0, buf, 0, buf.length, null);
|
|
309
135
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
let c = col;
|
|
313
|
-
while (c < line.length && /\w/.test(line[c] ?? ''))
|
|
314
|
-
c++;
|
|
315
|
-
while (c < line.length && /\W/.test(line[c] ?? ''))
|
|
316
|
-
c++;
|
|
317
|
-
col = c;
|
|
318
|
-
render();
|
|
319
|
-
return;
|
|
136
|
+
catch {
|
|
137
|
+
break;
|
|
320
138
|
}
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return;
|
|
139
|
+
if (n === 0)
|
|
140
|
+
break;
|
|
141
|
+
const b = buf[0];
|
|
142
|
+
if (b === undefined)
|
|
143
|
+
break;
|
|
144
|
+
// Ctrl+C → cancel
|
|
145
|
+
if (b === 3) {
|
|
146
|
+
process.stdout.write('\n');
|
|
147
|
+
return null;
|
|
331
148
|
}
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return;
|
|
149
|
+
// Ctrl+D → submit
|
|
150
|
+
if (b === 4) {
|
|
151
|
+
process.stdout.write('\n');
|
|
152
|
+
const text = lines.join('\n').trim();
|
|
153
|
+
if (!text && initialText.trim())
|
|
154
|
+
return initialText.trim();
|
|
155
|
+
return text || null;
|
|
340
156
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
c--;
|
|
348
|
-
col = c;
|
|
349
|
-
render();
|
|
350
|
-
return;
|
|
157
|
+
// Enter → new line
|
|
158
|
+
if (b === 13 || b === 10) {
|
|
159
|
+
process.stdout.write('\n');
|
|
160
|
+
li++;
|
|
161
|
+
lines.splice(li, 0, '');
|
|
162
|
+
continue;
|
|
351
163
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
164
|
+
// Backspace
|
|
165
|
+
if (b === 127 || b === 8) {
|
|
166
|
+
const cur = lines[li] ?? '';
|
|
167
|
+
if (cur.length > 0) {
|
|
168
|
+
// Delete within current line
|
|
169
|
+
lines[li] = cur.slice(0, -1);
|
|
170
|
+
process.stdout.write('\b \b');
|
|
355
171
|
}
|
|
356
|
-
else if (
|
|
357
|
-
|
|
358
|
-
|
|
172
|
+
else if (li > 0) {
|
|
173
|
+
// At start of line → merge with previous line
|
|
174
|
+
lines.splice(li, 1);
|
|
175
|
+
li--;
|
|
176
|
+
fullRedraw();
|
|
359
177
|
}
|
|
360
|
-
|
|
361
|
-
return;
|
|
178
|
+
continue;
|
|
362
179
|
}
|
|
363
|
-
//
|
|
364
|
-
if (
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
return;
|
|
180
|
+
// Ctrl+W → delete word
|
|
181
|
+
if (b === 23) {
|
|
182
|
+
deleteWord();
|
|
183
|
+
continue;
|
|
368
184
|
}
|
|
369
|
-
//
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
185
|
+
// Ctrl+U → clear current line
|
|
186
|
+
if (b === 21) {
|
|
187
|
+
lines[li] = '';
|
|
188
|
+
process.stdout.write('\r\u001B[K');
|
|
189
|
+
continue;
|
|
374
190
|
}
|
|
375
|
-
//
|
|
376
|
-
if (
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
// Join with next line
|
|
383
|
-
lines[row] = line + (lines[row + 1] ?? '');
|
|
384
|
-
lines.splice(row + 1, 1);
|
|
385
|
-
}
|
|
386
|
-
render();
|
|
387
|
-
return;
|
|
191
|
+
// Ctrl+L → clear all, restart
|
|
192
|
+
if (b === 12) {
|
|
193
|
+
lines.length = 0;
|
|
194
|
+
lines.push('');
|
|
195
|
+
li = 0;
|
|
196
|
+
fullRedraw();
|
|
197
|
+
continue;
|
|
388
198
|
}
|
|
389
|
-
// Backspace
|
|
390
|
-
if (
|
|
391
|
-
if (
|
|
392
|
-
|
|
393
|
-
lines[row] = line.slice(0, col - 1) + line.slice(col);
|
|
394
|
-
col--;
|
|
199
|
+
// ESC sequences → Option+Backspace (ESC+DEL) = del word
|
|
200
|
+
if (b === 27) {
|
|
201
|
+
if (n >= 2 && buf[1] === 0x7f) {
|
|
202
|
+
deleteWord();
|
|
395
203
|
}
|
|
396
|
-
|
|
397
|
-
// Join with previous line
|
|
398
|
-
col = lineAt(row - 1).length;
|
|
399
|
-
lines[row - 1] = lineAt(row - 1) + lineAt(row);
|
|
400
|
-
lines.splice(row, 1);
|
|
401
|
-
row--;
|
|
402
|
-
}
|
|
403
|
-
render();
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
// Enter
|
|
407
|
-
if (key === '\r' || key === '\n') {
|
|
408
|
-
const line = lineAt(row);
|
|
409
|
-
const before = line.slice(0, col);
|
|
410
|
-
const after = line.slice(col);
|
|
411
|
-
lines[row] = before;
|
|
412
|
-
lines.splice(row + 1, 0, after);
|
|
413
|
-
row++;
|
|
414
|
-
col = 0;
|
|
415
|
-
render();
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
// Tab → 2 spaces
|
|
419
|
-
if (key === '\t') {
|
|
420
|
-
const line = lineAt(row);
|
|
421
|
-
lines[row] = line.slice(0, col) + ' ' + line.slice(col);
|
|
422
|
-
col += 2;
|
|
423
|
-
render();
|
|
424
|
-
return;
|
|
204
|
+
continue;
|
|
425
205
|
}
|
|
426
206
|
// Printable characters
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
lines[row] = line.slice(0, col) + ch + line.slice(col);
|
|
432
|
-
col++;
|
|
433
|
-
}
|
|
207
|
+
if (b >= 32) {
|
|
208
|
+
const ch = buf.toString('utf8', 0, n);
|
|
209
|
+
lines[li] = (lines[li] ?? '') + ch;
|
|
210
|
+
process.stdout.write(ch);
|
|
434
211
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
if (process.stdin.isTTY) {
|
|
216
|
+
process.stdin.setRawMode(false);
|
|
217
|
+
}
|
|
218
|
+
rl.resume();
|
|
219
|
+
}
|
|
220
|
+
const text = lines.join('\n').trim();
|
|
221
|
+
if (!text && initialText.trim())
|
|
222
|
+
return initialText.trim();
|
|
223
|
+
return text || null;
|
|
439
224
|
};
|
|
440
225
|
/**
|
|
441
|
-
*
|
|
442
|
-
*
|
|
226
|
+
* Inline multi-line text editor using the system's built-in terminal editor.
|
|
227
|
+
* Opens nano (macOS/Linux) or notepad (Windows) with the initial text.
|
|
228
|
+
*
|
|
229
|
+
* Kept async (returns Promise) so all existing callers using
|
|
230
|
+
* `await editInline(...)` continue to work without changes.
|
|
443
231
|
*/
|
|
444
|
-
export const
|
|
232
|
+
export const editInline = (initialText, label = 'Edit Message', _syntax = '') => {
|
|
233
|
+
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
234
|
+
process.stdin.setRawMode(false);
|
|
235
|
+
}
|
|
236
|
+
process.stdout.write('\u001B[?25h');
|
|
237
|
+
console.log(`\n \u001B[36m${label}\u001B[0m`);
|
|
445
238
|
const tmpDir = os.tmpdir();
|
|
446
|
-
const tmpPath = path.join(tmpDir,
|
|
239
|
+
const tmpPath = path.join(tmpDir, `geeto-${Date.now()}.md`);
|
|
447
240
|
try {
|
|
448
241
|
fs.writeFileSync(tmpPath, initialText, { encoding: 'utf8' });
|
|
449
242
|
}
|
|
450
243
|
catch {
|
|
451
|
-
return
|
|
244
|
+
return Promise.resolve(null);
|
|
452
245
|
}
|
|
453
|
-
const editor = process.
|
|
246
|
+
const editor = process.platform === 'win32' ? 'notepad' : 'nano';
|
|
454
247
|
try {
|
|
455
248
|
spawnSync(editor, [tmpPath], { stdio: 'inherit' });
|
|
456
|
-
const edited = fs.readFileSync(tmpPath, { encoding: 'utf8' });
|
|
457
|
-
|
|
249
|
+
const edited = fs.readFileSync(tmpPath, { encoding: 'utf8' }).trim();
|
|
250
|
+
if (!edited)
|
|
251
|
+
return Promise.resolve(null);
|
|
252
|
+
return Promise.resolve(edited);
|
|
458
253
|
}
|
|
459
254
|
catch {
|
|
460
|
-
|
|
461
|
-
|
|
255
|
+
return Promise.resolve(null);
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
try {
|
|
259
|
+
fs.unlinkSync(tmpPath);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// ignore cleanup errors
|
|
263
|
+
}
|
|
462
264
|
}
|
|
463
265
|
};
|
|
464
266
|
/**
|