geeto 0.9.0 → 0.10.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 +77 -18
- package/lib/api/copilot-sdk.d.ts.map +1 -1
- package/lib/api/copilot-sdk.js +63 -36
- package/lib/api/copilot-sdk.js.map +1 -1
- package/lib/api/copilot.d.ts.map +1 -1
- package/lib/api/copilot.js +6 -6
- package/lib/api/copilot.js.map +1 -1
- package/lib/api/gemini.d.ts.map +1 -1
- package/lib/api/gemini.js +2 -2
- package/lib/api/gemini.js.map +1 -1
- package/lib/api/groq-sdk.d.ts +11 -0
- package/lib/api/groq-sdk.d.ts.map +1 -0
- package/lib/api/groq-sdk.js +131 -0
- package/lib/api/groq-sdk.js.map +1 -0
- package/lib/api/groq.d.ts +10 -0
- package/lib/api/groq.d.ts.map +1 -0
- package/lib/api/groq.js +36 -0
- package/lib/api/groq.js.map +1 -0
- package/lib/api/openrouter-sdk.d.ts.map +1 -1
- package/lib/api/openrouter-sdk.js +58 -73
- package/lib/api/openrouter-sdk.js.map +1 -1
- package/lib/api/openrouter.d.ts.map +1 -1
- package/lib/api/openrouter.js +2 -2
- package/lib/api/openrouter.js.map +1 -1
- package/lib/api/trello.d.ts +16 -1
- package/lib/api/trello.d.ts.map +1 -1
- package/lib/api/trello.js +90 -2
- package/lib/api/trello.js.map +1 -1
- package/lib/cli/input.d.ts +1 -9
- package/lib/cli/input.d.ts.map +1 -1
- package/lib/cli/input.js +428 -169
- package/lib/cli/input.js.map +1 -1
- package/lib/cli/menu.d.ts.map +1 -1
- package/lib/cli/menu.js +33 -20
- package/lib/cli/menu.js.map +1 -1
- package/lib/core/copilot-setup.d.ts +6 -1
- package/lib/core/copilot-setup.d.ts.map +1 -1
- package/lib/core/copilot-setup.js +40 -34
- package/lib/core/copilot-setup.js.map +1 -1
- package/lib/core/gemini-setup.d.ts.map +1 -1
- package/lib/core/gemini-setup.js +7 -13
- package/lib/core/gemini-setup.js.map +1 -1
- package/lib/core/github-setup.d.ts.map +1 -1
- package/lib/core/github-setup.js +13 -7
- package/lib/core/github-setup.js.map +1 -1
- package/lib/core/gitlab-setup.d.ts.map +1 -1
- package/lib/core/gitlab-setup.js +13 -6
- package/lib/core/gitlab-setup.js.map +1 -1
- package/lib/core/groq-setup.d.ts +5 -0
- package/lib/core/groq-setup.d.ts.map +1 -0
- package/lib/core/groq-setup.js +67 -0
- package/lib/core/groq-setup.js.map +1 -0
- package/lib/core/openrouter-setup.d.ts.map +1 -1
- package/lib/core/openrouter-setup.js +11 -21
- package/lib/core/openrouter-setup.js.map +1 -1
- package/lib/core/setup.d.ts +2 -1
- package/lib/core/setup.d.ts.map +1 -1
- package/lib/core/setup.js +44 -7
- package/lib/core/setup.js.map +1 -1
- package/lib/index.js +25 -0
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +9 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/utils/ai-workflow.d.ts +10 -2
- package/lib/utils/ai-workflow.d.ts.map +1 -1
- package/lib/utils/ai-workflow.js +15 -0
- package/lib/utils/ai-workflow.js.map +1 -1
- package/lib/utils/branch-naming.d.ts +2 -1
- package/lib/utils/branch-naming.d.ts.map +1 -1
- package/lib/utils/branch-naming.js +114 -68
- package/lib/utils/branch-naming.js.map +1 -1
- package/lib/utils/config.d.ts +11 -3
- package/lib/utils/config.d.ts.map +1 -1
- package/lib/utils/config.js +46 -18
- package/lib/utils/config.js.map +1 -1
- package/lib/utils/exec.d.ts.map +1 -1
- package/lib/utils/exec.js +10 -2
- package/lib/utils/exec.js.map +1 -1
- package/lib/utils/git-ai-errors.d.ts.map +1 -1
- package/lib/utils/git-ai-errors.js +5 -1
- package/lib/utils/git-ai-errors.js.map +1 -1
- package/lib/utils/git-ai.d.ts +8 -7
- package/lib/utils/git-ai.d.ts.map +1 -1
- package/lib/utils/git-ai.js +239 -117
- package/lib/utils/git-ai.js.map +1 -1
- package/lib/utils/logging.d.ts.map +1 -1
- package/lib/utils/logging.js +14 -3
- package/lib/utils/logging.js.map +1 -1
- package/lib/utils/menu-builders.js +1 -1
- package/lib/utils/menu-builders.js.map +1 -1
- package/lib/utils/prompts-embedded.d.ts.map +1 -1
- package/lib/utils/prompts-embedded.js +0 -81
- package/lib/utils/prompts-embedded.js.map +1 -1
- package/lib/utils/scramble.d.ts.map +1 -1
- package/lib/utils/scramble.js +14 -3
- package/lib/utils/scramble.js.map +1 -1
- package/lib/utils/state.d.ts.map +1 -1
- package/lib/utils/state.js +12 -1
- package/lib/utils/state.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.d.ts.map +1 -1
- package/lib/version.js +1 -1
- package/lib/version.js.map +1 -1
- package/lib/workflows/ai-provider.d.ts +3 -1
- package/lib/workflows/ai-provider.d.ts.map +1 -1
- package/lib/workflows/ai-provider.js +8 -2
- package/lib/workflows/ai-provider.js.map +1 -1
- package/lib/workflows/branch-helpers.d.ts.map +1 -1
- package/lib/workflows/branch-helpers.js +142 -71
- package/lib/workflows/branch-helpers.js.map +1 -1
- package/lib/workflows/branch.d.ts.map +1 -1
- package/lib/workflows/branch.js +97 -58
- package/lib/workflows/branch.js.map +1 -1
- package/lib/workflows/commit.d.ts +1 -1
- package/lib/workflows/commit.d.ts.map +1 -1
- package/lib/workflows/commit.js +97 -35
- package/lib/workflows/commit.js.map +1 -1
- package/lib/workflows/dry-run.d.ts.map +1 -1
- package/lib/workflows/dry-run.js +6 -0
- package/lib/workflows/dry-run.js.map +1 -1
- package/lib/workflows/issue.d.ts.map +1 -1
- package/lib/workflows/issue.js +12 -12
- package/lib/workflows/issue.js.map +1 -1
- package/lib/workflows/main-helpers.d.ts +3 -1
- package/lib/workflows/main-helpers.d.ts.map +1 -1
- package/lib/workflows/main-helpers.js +30 -26
- package/lib/workflows/main-helpers.js.map +1 -1
- package/lib/workflows/main.d.ts.map +1 -1
- package/lib/workflows/main.js +164 -13
- package/lib/workflows/main.js.map +1 -1
- package/lib/workflows/pr.d.ts.map +1 -1
- package/lib/workflows/pr.js +12 -12
- package/lib/workflows/pr.js.map +1 -1
- package/lib/workflows/release-merge.d.ts.map +1 -1
- package/lib/workflows/release-merge.js +65 -11
- package/lib/workflows/release-merge.js.map +1 -1
- package/lib/workflows/release-sync.d.ts.map +1 -1
- package/lib/workflows/release-sync.js +87 -12
- package/lib/workflows/release-sync.js.map +1 -1
- package/lib/workflows/release.d.ts.map +1 -1
- package/lib/workflows/release.js +130 -15
- package/lib/workflows/release.js.map +1 -1
- package/lib/workflows/repo-settings.d.ts.map +1 -1
- package/lib/workflows/repo-settings.js +35 -8
- package/lib/workflows/repo-settings.js.map +1 -1
- package/lib/workflows/reword.d.ts.map +1 -1
- package/lib/workflows/reword.js +60 -17
- package/lib/workflows/reword.js.map +1 -1
- package/lib/workflows/settings.d.ts +3 -1
- package/lib/workflows/settings.d.ts.map +1 -1
- package/lib/workflows/settings.js +393 -75
- package/lib/workflows/settings.js.map +1 -1
- package/lib/workflows/submodules.d.ts +6 -0
- package/lib/workflows/submodules.d.ts.map +1 -0
- package/lib/workflows/submodules.js +344 -0
- package/lib/workflows/submodules.js.map +1 -0
- package/lib/workflows/trello-menu.d.ts +0 -3
- package/lib/workflows/trello-menu.d.ts.map +1 -1
- package/lib/workflows/trello-menu.js +349 -19
- package/lib/workflows/trello-menu.js.map +1 -1
- package/package.json +13 -2
- package/lib/workflows/security-gate.d.ts +0 -8
- package/lib/workflows/security-gate.d.ts.map +0 -1
- package/lib/workflows/security-gate.js +0 -455
- package/lib/workflows/security-gate.js.map +0 -1
- package/prompts/security-gate-prompt.md +0 -80
package/lib/cli/input.js
CHANGED
|
@@ -4,12 +4,36 @@
|
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import os from 'node:os';
|
|
7
|
-
import path from 'node:path';
|
|
8
7
|
import readline from 'node:readline';
|
|
9
8
|
const rl = readline.createInterface({
|
|
10
9
|
input: process.stdin,
|
|
11
10
|
output: process.stdout,
|
|
12
11
|
});
|
|
12
|
+
const readStdinText = () => {
|
|
13
|
+
try {
|
|
14
|
+
return fs.readFileSync(0, 'utf8');
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const supportsStickyTerminalLayout = () => {
|
|
21
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
22
|
+
return false;
|
|
23
|
+
const term = process.env.TERM?.toLowerCase();
|
|
24
|
+
if (term === 'dumb')
|
|
25
|
+
return false;
|
|
26
|
+
if (process.platform !== 'win32')
|
|
27
|
+
return term !== undefined;
|
|
28
|
+
return [
|
|
29
|
+
process.env.WT_SESSION,
|
|
30
|
+
// cspell:ignore ANSICON
|
|
31
|
+
process.env.ANSICON,
|
|
32
|
+
process.env.ConEmuANSI === 'ON' ? '1' : undefined,
|
|
33
|
+
process.env.TERM_PROGRAM === 'vscode' ? '1' : undefined,
|
|
34
|
+
term !== undefined && term !== 'dumb' ? term : undefined,
|
|
35
|
+
].some(Boolean);
|
|
36
|
+
};
|
|
13
37
|
/**
|
|
14
38
|
* Ask a question and return user input
|
|
15
39
|
*/
|
|
@@ -62,7 +86,9 @@ export const confirm = (question, defaultYes = true) => {
|
|
|
62
86
|
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
63
87
|
process.stdin.setRawMode(false);
|
|
64
88
|
}
|
|
65
|
-
|
|
89
|
+
if (supportsStickyTerminalLayout()) {
|
|
90
|
+
process.stdout.write('\u001B[?25h');
|
|
91
|
+
}
|
|
66
92
|
// Non-TTY fallback — plain text input
|
|
67
93
|
if (!process.stdin.isTTY) {
|
|
68
94
|
const suffix = defaultYes ? ' (Y/n): ' : ' (y/N): ';
|
|
@@ -179,203 +205,436 @@ export const confirm = (question, defaultYes = true) => {
|
|
|
179
205
|
*
|
|
180
206
|
* Returns trimmed text or `null` when cancelled.
|
|
181
207
|
*/
|
|
182
|
-
export const
|
|
208
|
+
export const editMultiline = async (question, initialText = '') => {
|
|
183
209
|
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
184
210
|
process.stdin.setRawMode(false);
|
|
185
211
|
}
|
|
186
|
-
process.stdout.write('\u001B[?25h');
|
|
187
212
|
const isMac = process.platform === 'darwin';
|
|
188
213
|
const delWordHint = isMac ? '⌥⌫' : 'Ctrl+W';
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
214
|
+
const cleanQuestion = question.trim();
|
|
215
|
+
const hintText1 = 'Enter=newline | Ctrl+D=submit | Ctrl+C=cancel';
|
|
216
|
+
const hintText2 = `${delWordHint}=del word | Ctrl+U=del line | Ctrl+L=clear all`;
|
|
217
|
+
const compactHintText = `Enter=newline | Ctrl+D=submit | Ctrl+C=cancel | ${delWordHint}=del word | Ctrl+U=line | Ctrl+L=clear`;
|
|
218
|
+
const printPlainIntro = () => {
|
|
219
|
+
console.log(`\n? ${cleanQuestion}`);
|
|
220
|
+
if (initialText.trim()) {
|
|
221
|
+
console.log(' -- current --');
|
|
222
|
+
for (const l of initialText.split('\n')) {
|
|
223
|
+
console.log(` ${l}`);
|
|
224
|
+
}
|
|
225
|
+
console.log(' -- type below (empty = keep) --');
|
|
199
226
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (!process.stdin.isTTY) {
|
|
204
|
-
const r = spawnSync('cat', [], {
|
|
205
|
-
stdio: ['inherit', 'pipe', 'inherit'],
|
|
206
|
-
encoding: 'utf8',
|
|
207
|
-
});
|
|
208
|
-
const t = r.stdout?.trim() ?? '';
|
|
227
|
+
};
|
|
228
|
+
const getTextResult = (rawText) => {
|
|
229
|
+
const t = rawText.trim();
|
|
209
230
|
if (!t && initialText.trim())
|
|
210
231
|
return initialText.trim();
|
|
211
232
|
return t || null;
|
|
233
|
+
};
|
|
234
|
+
// Non-TTY fallback (piped input)
|
|
235
|
+
if (!process.stdin.isTTY) {
|
|
236
|
+
printPlainIntro();
|
|
237
|
+
return getTextResult(readStdinText());
|
|
212
238
|
}
|
|
239
|
+
if (!supportsStickyTerminalLayout()) {
|
|
240
|
+
printPlainIntro();
|
|
241
|
+
console.log(' Submit with EOF: Ctrl+D on Unix/macOS, Ctrl+Z then Enter on Windows.');
|
|
242
|
+
rl.pause();
|
|
243
|
+
const text = getTextResult(readStdinText());
|
|
244
|
+
rl.resume();
|
|
245
|
+
return text;
|
|
246
|
+
}
|
|
247
|
+
process.stdout.write('\u001B[?25h');
|
|
213
248
|
// Pause readline so fs.readSync can use fd 0
|
|
214
249
|
rl.pause();
|
|
215
250
|
process.stdin.setRawMode(true);
|
|
216
|
-
|
|
217
|
-
let
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
251
|
+
let rows = process.stdout.rows ?? 24;
|
|
252
|
+
let columns = process.stdout.columns ?? 80;
|
|
253
|
+
let footerRows = rows < 6 ? 1 : 2;
|
|
254
|
+
let scrollTop = rows < 6 ? 2 : 3;
|
|
255
|
+
let scrollBottom = Math.max(scrollTop, rows - footerRows);
|
|
256
|
+
let viewportTop = 0;
|
|
257
|
+
let resizeTimer;
|
|
258
|
+
let stickyTimer;
|
|
259
|
+
const updateTerminalSize = () => {
|
|
260
|
+
rows = process.stdout.rows ?? 24;
|
|
261
|
+
columns = process.stdout.columns ?? 80;
|
|
262
|
+
footerRows = rows < 6 ? 1 : 2;
|
|
263
|
+
scrollTop = rows < 6 ? 2 : 3;
|
|
264
|
+
scrollBottom = Math.max(scrollTop, rows - footerRows);
|
|
265
|
+
};
|
|
266
|
+
const printHeader = () => {
|
|
267
|
+
process.stdout.write(`\u001B[1;1H\u001B[2K\u001B[36m?\u001B[0m ${cleanQuestion}`);
|
|
268
|
+
};
|
|
269
|
+
const printHints = () => {
|
|
270
|
+
const formatHint = (text) => {
|
|
271
|
+
const maxLength = Math.max(1, columns - 2);
|
|
272
|
+
const trimmed = text.length > maxLength
|
|
273
|
+
? maxLength <= 3
|
|
274
|
+
? '.'.repeat(maxLength)
|
|
275
|
+
: text.slice(0, maxLength - 3) + '...'
|
|
276
|
+
: text;
|
|
277
|
+
return ` \u001B[90m${trimmed}\u001B[0m`;
|
|
278
|
+
};
|
|
279
|
+
if (footerRows === 1) {
|
|
280
|
+
process.stdout.write(`\u001B[${rows};1H\u001B[2K${formatHint(compactHintText)}`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
process.stdout.write(`\u001B[${rows - 1};1H\u001B[2K${formatHint(hintText1)}`);
|
|
284
|
+
process.stdout.write(`\u001B[${rows};1H\u001B[2K${formatHint(hintText2)}`);
|
|
285
|
+
};
|
|
286
|
+
const refreshStickyChrome = () => {
|
|
287
|
+
const currentRows = process.stdout.rows ?? 24;
|
|
288
|
+
const currentColumns = process.stdout.columns ?? 80;
|
|
289
|
+
if (currentRows !== rows || currentColumns !== columns) {
|
|
290
|
+
fullRedraw(true);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const lines = initialText.trim() ? initialText.split('\n') : [''];
|
|
294
|
+
let li = lines.length - 1;
|
|
295
|
+
let ci = (lines[li] ?? '').length;
|
|
296
|
+
const currentLine = () => lines[li] ?? '';
|
|
297
|
+
const isWhitespace = (ch) => /\s/.test(ch);
|
|
298
|
+
const contentHeight = () => Math.max(1, scrollBottom - scrollTop + 1);
|
|
299
|
+
// Keep one column free so redraws never trigger terminal auto-wrap by writing
|
|
300
|
+
// exactly at the right edge. That keeps cursor math stable for long AI text.
|
|
301
|
+
const wrapWidth = () => Math.max(1, columns - 1);
|
|
302
|
+
const visualRowsForLine = (line) => Math.max(1, Math.ceil(line.length / wrapWidth()));
|
|
303
|
+
const visualRowBeforeLine = (lineIndex) => {
|
|
304
|
+
let total = 0;
|
|
305
|
+
for (let i = 0; i < lineIndex; i++) {
|
|
306
|
+
total += visualRowsForLine(lines[i] ?? '');
|
|
227
307
|
}
|
|
308
|
+
return total;
|
|
228
309
|
};
|
|
310
|
+
const cursorVisualRow = () => visualRowBeforeLine(li) + Math.floor(ci / wrapWidth());
|
|
311
|
+
const cursorVisualColumn = () => ci % wrapWidth();
|
|
312
|
+
const ensureCursorVisible = () => {
|
|
313
|
+
const row = cursorVisualRow();
|
|
314
|
+
if (row < viewportTop) {
|
|
315
|
+
viewportTop = row;
|
|
316
|
+
}
|
|
317
|
+
else if (row >= viewportTop + contentHeight()) {
|
|
318
|
+
viewportTop = row - contentHeight() + 1;
|
|
319
|
+
}
|
|
320
|
+
viewportTop = Math.max(0, viewportTop);
|
|
321
|
+
};
|
|
322
|
+
const getVisualLine = (targetRow) => {
|
|
323
|
+
let row = 0;
|
|
324
|
+
const width = wrapWidth();
|
|
325
|
+
for (const line of lines) {
|
|
326
|
+
const visualRows = visualRowsForLine(line);
|
|
327
|
+
if (targetRow < row + visualRows) {
|
|
328
|
+
const offset = (targetRow - row) * width;
|
|
329
|
+
return line.slice(offset, offset + width);
|
|
330
|
+
}
|
|
331
|
+
row += visualRows;
|
|
332
|
+
}
|
|
333
|
+
return '';
|
|
334
|
+
};
|
|
335
|
+
const totalVisualRows = () => {
|
|
336
|
+
let total = 0;
|
|
337
|
+
for (const line of lines) {
|
|
338
|
+
total += visualRowsForLine(line);
|
|
339
|
+
}
|
|
340
|
+
return Math.max(1, total);
|
|
341
|
+
};
|
|
342
|
+
const setCursorFromVisualPosition = (targetRow, targetColumn) => {
|
|
343
|
+
let row = 0;
|
|
344
|
+
const width = wrapWidth();
|
|
345
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
346
|
+
const visualRows = visualRowsForLine(line);
|
|
347
|
+
if (targetRow < row + visualRows) {
|
|
348
|
+
const offset = (targetRow - row) * width;
|
|
349
|
+
li = lineIndex;
|
|
350
|
+
ci = Math.min(line.length, offset + targetColumn);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
row += visualRows;
|
|
354
|
+
}
|
|
355
|
+
li = lines.length - 1;
|
|
356
|
+
ci = currentLine().length;
|
|
357
|
+
};
|
|
358
|
+
const moveCursorToInput = () => {
|
|
359
|
+
ensureCursorVisible();
|
|
360
|
+
const row = Math.min(scrollBottom, scrollTop + cursorVisualRow() - viewportTop);
|
|
361
|
+
const col = Math.max(1, cursorVisualColumn() + 1);
|
|
362
|
+
process.stdout.write(`\u001B[${row};${col}H`);
|
|
363
|
+
};
|
|
364
|
+
/** Redraw editor layout; header+hints stay fixed around the scrollable content area. */
|
|
365
|
+
const fullRedraw = (clearScreen = false) => {
|
|
366
|
+
updateTerminalSize();
|
|
367
|
+
process.stdout.write('\u001B[r');
|
|
368
|
+
if (clearScreen)
|
|
369
|
+
process.stdout.write('\u001B[2J');
|
|
370
|
+
ensureCursorVisible();
|
|
371
|
+
printHeader();
|
|
372
|
+
for (let row = 0; row < contentHeight(); row++) {
|
|
373
|
+
process.stdout.write(`\u001B[${scrollTop + row};1H\u001B[2K`);
|
|
374
|
+
process.stdout.write(getVisualLine(viewportTop + row));
|
|
375
|
+
}
|
|
376
|
+
printHints();
|
|
377
|
+
moveCursorToInput();
|
|
378
|
+
};
|
|
379
|
+
const handleResize = () => {
|
|
380
|
+
if (resizeTimer)
|
|
381
|
+
clearTimeout(resizeTimer);
|
|
382
|
+
resizeTimer = setTimeout(() => {
|
|
383
|
+
fullRedraw(true);
|
|
384
|
+
}, 25);
|
|
385
|
+
};
|
|
386
|
+
process.on('SIGWINCH', handleResize);
|
|
387
|
+
// Use the alternate screen so sticky multiline editing does not fight shell scrollback.
|
|
388
|
+
process.stdout.write('\u001B[?1049h');
|
|
389
|
+
fullRedraw(true);
|
|
390
|
+
stickyTimer = setInterval(refreshStickyChrome, 150);
|
|
229
391
|
/** Delete word backwards on current line */
|
|
230
392
|
const deleteWord = () => {
|
|
231
|
-
const cur =
|
|
232
|
-
if (!cur)
|
|
393
|
+
const cur = currentLine();
|
|
394
|
+
if (!cur || ci === 0)
|
|
233
395
|
return;
|
|
234
|
-
const
|
|
396
|
+
const before = cur.slice(0, ci);
|
|
397
|
+
const after = cur.slice(ci);
|
|
398
|
+
const stripped = before.replace(/\s+$/, '');
|
|
235
399
|
const sp = stripped.lastIndexOf(' ');
|
|
236
|
-
|
|
237
|
-
|
|
400
|
+
const nextBefore = sp === -1 ? '' : stripped.slice(0, sp + 1);
|
|
401
|
+
lines[li] = nextBefore + after;
|
|
402
|
+
ci = nextBefore.length;
|
|
403
|
+
fullRedraw();
|
|
238
404
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
catch {
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
if (n === 0)
|
|
249
|
-
break;
|
|
250
|
-
const b = buf[0];
|
|
251
|
-
if (b === undefined)
|
|
252
|
-
break;
|
|
253
|
-
// Ctrl+C → cancel
|
|
254
|
-
if (b === 3) {
|
|
255
|
-
process.stdout.write('\n');
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
// Escape (standalone, not arrow sequence) → cancel
|
|
259
|
-
if (b === 27 && (n === 1 || buf[1] !== 0x5b)) {
|
|
260
|
-
process.stdout.write('\n');
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
// Ctrl+D → submit
|
|
264
|
-
if (b === 4) {
|
|
265
|
-
process.stdout.write('\n');
|
|
266
|
-
const text = lines.join('\n').trim();
|
|
267
|
-
if (!text && initialText.trim())
|
|
268
|
-
return initialText.trim();
|
|
269
|
-
return text || null;
|
|
405
|
+
const moveWordLeft = () => {
|
|
406
|
+
if (ci === 0) {
|
|
407
|
+
if (li > 0) {
|
|
408
|
+
li--;
|
|
409
|
+
ci = currentLine().length;
|
|
270
410
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
411
|
+
moveCursorToInput();
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const cur = currentLine();
|
|
415
|
+
let nextCursor = ci;
|
|
416
|
+
while (nextCursor > 0 && isWhitespace(cur[nextCursor - 1] ?? ''))
|
|
417
|
+
nextCursor--;
|
|
418
|
+
while (nextCursor > 0 && !isWhitespace(cur[nextCursor - 1] ?? ''))
|
|
419
|
+
nextCursor--;
|
|
420
|
+
ci = nextCursor;
|
|
421
|
+
moveCursorToInput();
|
|
422
|
+
};
|
|
423
|
+
const moveWordRight = () => {
|
|
424
|
+
const cur = currentLine();
|
|
425
|
+
if (ci >= cur.length) {
|
|
426
|
+
if (li < lines.length - 1) {
|
|
274
427
|
li++;
|
|
275
|
-
|
|
276
|
-
continue;
|
|
428
|
+
ci = 0;
|
|
277
429
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
430
|
+
moveCursorToInput();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
let nextCursor = ci;
|
|
434
|
+
while (nextCursor < cur.length && !isWhitespace(cur[nextCursor] ?? ''))
|
|
435
|
+
nextCursor++;
|
|
436
|
+
while (nextCursor < cur.length && isWhitespace(cur[nextCursor] ?? ''))
|
|
437
|
+
nextCursor++;
|
|
438
|
+
ci = nextCursor;
|
|
439
|
+
moveCursorToInput();
|
|
440
|
+
};
|
|
441
|
+
const moveVisualLine = (delta) => {
|
|
442
|
+
const nextRow = Math.min(Math.max(0, cursorVisualRow() + delta), totalVisualRows() - 1);
|
|
443
|
+
setCursorFromVisualPosition(nextRow, cursorVisualColumn());
|
|
444
|
+
moveCursorToInput();
|
|
445
|
+
};
|
|
446
|
+
const getFinalText = () => {
|
|
447
|
+
const text = lines.join('\n').trim();
|
|
448
|
+
if (!text && initialText.trim())
|
|
449
|
+
return initialText.trim();
|
|
450
|
+
return text || null;
|
|
451
|
+
};
|
|
452
|
+
return await new Promise((resolve) => {
|
|
453
|
+
let done = false;
|
|
454
|
+
const finish = (value) => {
|
|
455
|
+
if (done)
|
|
456
|
+
return;
|
|
457
|
+
done = true;
|
|
458
|
+
process.stdin.off('data', handleData);
|
|
459
|
+
if (process.stdin.isTTY) {
|
|
460
|
+
process.stdin.setRawMode(false);
|
|
461
|
+
}
|
|
462
|
+
if (resizeTimer)
|
|
463
|
+
clearTimeout(resizeTimer);
|
|
464
|
+
if (stickyTimer)
|
|
465
|
+
clearInterval(stickyTimer);
|
|
466
|
+
process.off('SIGWINCH', handleResize);
|
|
467
|
+
process.stdout.write('\u001B[r\u001B[?1049l');
|
|
468
|
+
process.stdin.pause();
|
|
469
|
+
resolve(value);
|
|
470
|
+
};
|
|
471
|
+
function handleData(chunk) {
|
|
472
|
+
const input = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
473
|
+
const n = input.length;
|
|
474
|
+
for (let offset = 0; offset < n; offset++) {
|
|
475
|
+
const b = input[offset];
|
|
476
|
+
if (b === undefined)
|
|
477
|
+
break;
|
|
478
|
+
// Ctrl+C → cancel
|
|
479
|
+
if (b === 3) {
|
|
480
|
+
process.stdout.write('\n');
|
|
481
|
+
finish(null);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
// Ctrl+D → submit
|
|
485
|
+
if (b === 4) {
|
|
486
|
+
process.stdout.write('\n');
|
|
487
|
+
finish(getFinalText());
|
|
488
|
+
return;
|
|
285
489
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
li
|
|
490
|
+
// Enter → new line
|
|
491
|
+
if (b === 13 || b === 10) {
|
|
492
|
+
const cur = currentLine();
|
|
493
|
+
lines[li] = cur.slice(0, ci);
|
|
494
|
+
li++;
|
|
495
|
+
lines.splice(li, 0, cur.slice(ci));
|
|
496
|
+
ci = 0;
|
|
290
497
|
fullRedraw();
|
|
498
|
+
continue;
|
|
291
499
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (b === 27) {
|
|
315
|
-
if (n >= 2 && buf[1] === 0x7f) {
|
|
500
|
+
// Backspace
|
|
501
|
+
if (b === 127 || b === 8) {
|
|
502
|
+
const cur = currentLine();
|
|
503
|
+
if (ci > 0) {
|
|
504
|
+
// Delete within current line
|
|
505
|
+
lines[li] = cur.slice(0, ci - 1) + cur.slice(ci);
|
|
506
|
+
ci--;
|
|
507
|
+
fullRedraw();
|
|
508
|
+
}
|
|
509
|
+
else if (li > 0) {
|
|
510
|
+
// At start of line → merge with previous line
|
|
511
|
+
const previousLength = (lines[li - 1] ?? '').length;
|
|
512
|
+
lines[li - 1] = (lines[li - 1] ?? '') + cur;
|
|
513
|
+
lines.splice(li, 1);
|
|
514
|
+
li--;
|
|
515
|
+
ci = previousLength;
|
|
516
|
+
fullRedraw();
|
|
517
|
+
}
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
// Ctrl+W → delete word
|
|
521
|
+
if (b === 23) {
|
|
316
522
|
deleteWord();
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
// Ctrl+U → clear current line
|
|
526
|
+
if (b === 21) {
|
|
527
|
+
lines[li] = '';
|
|
528
|
+
ci = 0;
|
|
529
|
+
fullRedraw();
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
// Ctrl+L → clear all, restart
|
|
533
|
+
if (b === 12) {
|
|
534
|
+
lines.length = 0;
|
|
535
|
+
lines.push('');
|
|
536
|
+
li = 0;
|
|
537
|
+
ci = 0;
|
|
538
|
+
fullRedraw();
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
// ESC sequences → Option+Backspace / Option+Arrow / Ctrl+Arrow
|
|
542
|
+
if (b === 27) {
|
|
543
|
+
const next = input[offset + 1];
|
|
544
|
+
if (next === 0x7f) {
|
|
545
|
+
deleteWord();
|
|
546
|
+
offset += 1;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (next === 0x62) {
|
|
550
|
+
moveWordLeft();
|
|
551
|
+
offset += 1;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (next === 0x66) {
|
|
555
|
+
moveWordRight();
|
|
556
|
+
offset += 1;
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (next === 0x5b) {
|
|
560
|
+
let sequenceEnd = offset + 2;
|
|
561
|
+
while (sequenceEnd < n) {
|
|
562
|
+
const value = input[sequenceEnd];
|
|
563
|
+
if (value !== undefined && value >= 0x40 && value <= 0x7e)
|
|
564
|
+
break;
|
|
565
|
+
sequenceEnd++;
|
|
566
|
+
}
|
|
567
|
+
const code = input[sequenceEnd];
|
|
568
|
+
const sequence = input.toString('utf8', offset, sequenceEnd + 1);
|
|
569
|
+
const isWordArrow = sequence.includes(';3') ||
|
|
570
|
+
sequence.includes(';5') ||
|
|
571
|
+
/^\u001B\[[35][CD]$/.test(sequence);
|
|
572
|
+
switch (code) {
|
|
573
|
+
case 0x44: {
|
|
574
|
+
if (isWordArrow) {
|
|
575
|
+
moveWordLeft();
|
|
576
|
+
}
|
|
577
|
+
else if (ci > 0) {
|
|
578
|
+
ci--;
|
|
579
|
+
}
|
|
580
|
+
else if (li > 0) {
|
|
581
|
+
li--;
|
|
582
|
+
ci = currentLine().length;
|
|
583
|
+
}
|
|
584
|
+
moveCursorToInput();
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
case 0x43: {
|
|
588
|
+
if (isWordArrow) {
|
|
589
|
+
moveWordRight();
|
|
590
|
+
}
|
|
591
|
+
else if (ci < currentLine().length) {
|
|
592
|
+
ci++;
|
|
593
|
+
}
|
|
594
|
+
else if (li < lines.length - 1) {
|
|
595
|
+
li++;
|
|
596
|
+
ci = 0;
|
|
597
|
+
}
|
|
598
|
+
moveCursorToInput();
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
case 0x41: {
|
|
602
|
+
moveVisualLine(-1);
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
case 0x42: {
|
|
606
|
+
moveVisualLine(1);
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
offset = sequenceEnd;
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
process.stdout.write('\n');
|
|
614
|
+
finish(null);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
// Printable text segment. A raw read can contain text plus control keys.
|
|
618
|
+
if (b >= 32) {
|
|
619
|
+
let end = offset + 1;
|
|
620
|
+
while (end < n) {
|
|
621
|
+
const next = input[end];
|
|
622
|
+
if (next === undefined || next < 32 || next === 127 || next === 27)
|
|
623
|
+
break;
|
|
624
|
+
end++;
|
|
625
|
+
}
|
|
626
|
+
const ch = input.toString('utf8', offset, end);
|
|
627
|
+
const cur = currentLine();
|
|
628
|
+
lines[li] = cur.slice(0, ci) + ch + cur.slice(ci);
|
|
629
|
+
ci += ch.length;
|
|
630
|
+
fullRedraw();
|
|
631
|
+
offset = end - 1;
|
|
317
632
|
}
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
// Printable characters
|
|
321
|
-
if (b >= 32) {
|
|
322
|
-
const ch = buf.toString('utf8', 0, n);
|
|
323
|
-
lines[li] = (lines[li] ?? '') + ch;
|
|
324
|
-
process.stdout.write(ch);
|
|
325
633
|
}
|
|
326
634
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
process.stdin.setRawMode(false);
|
|
331
|
-
}
|
|
332
|
-
rl.resume();
|
|
333
|
-
}
|
|
334
|
-
const text = lines.join('\n').trim();
|
|
335
|
-
if (!text && initialText.trim())
|
|
336
|
-
return initialText.trim();
|
|
337
|
-
return text || null;
|
|
338
|
-
};
|
|
339
|
-
/**
|
|
340
|
-
* Inline multi-line text editor using the system's terminal editor.
|
|
341
|
-
* Opens vim (macOS/Linux) or notepad (Windows) with the initial text.
|
|
342
|
-
*
|
|
343
|
-
* Kept async (returns Promise) so all existing callers using
|
|
344
|
-
* `await editInline(...)` continue to work without changes.
|
|
345
|
-
*/
|
|
346
|
-
export const editInline = (initialText, label = 'Edit Message', _syntax = '') => {
|
|
347
|
-
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
348
|
-
process.stdin.setRawMode(false);
|
|
349
|
-
}
|
|
350
|
-
process.stdout.write('\u001B[?25h');
|
|
351
|
-
console.log(`\n \u001B[36m${label}\u001B[0m`);
|
|
352
|
-
const tmpDir = os.tmpdir();
|
|
353
|
-
const tmpPath = path.join(tmpDir, `geeto-${Date.now()}.md`);
|
|
354
|
-
try {
|
|
355
|
-
fs.writeFileSync(tmpPath, initialText, { encoding: 'utf8' });
|
|
356
|
-
}
|
|
357
|
-
catch {
|
|
358
|
-
return Promise.resolve(null);
|
|
359
|
-
}
|
|
360
|
-
const editor = process.platform === 'win32' ? 'notepad' : 'vim';
|
|
361
|
-
try {
|
|
362
|
-
spawnSync(editor, [tmpPath], { stdio: 'inherit' });
|
|
363
|
-
const edited = fs.readFileSync(tmpPath, { encoding: 'utf8' }).trim();
|
|
364
|
-
if (!edited)
|
|
365
|
-
return Promise.resolve(null);
|
|
366
|
-
return Promise.resolve(edited);
|
|
367
|
-
}
|
|
368
|
-
catch {
|
|
369
|
-
return Promise.resolve(null);
|
|
370
|
-
}
|
|
371
|
-
finally {
|
|
372
|
-
try {
|
|
373
|
-
fs.unlinkSync(tmpPath);
|
|
374
|
-
}
|
|
375
|
-
catch {
|
|
376
|
-
// ignore cleanup errors
|
|
377
|
-
}
|
|
378
|
-
}
|
|
635
|
+
process.stdin.on('data', handleData);
|
|
636
|
+
process.stdin.resume();
|
|
637
|
+
});
|
|
379
638
|
};
|
|
380
639
|
/**
|
|
381
640
|
* Close the readline interface
|