decorated-pi 0.7.3 → 0.8.1
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 +3 -3
- package/commands/usage.ts +5 -1
- package/hooks/compaction.ts +11 -146
- package/hooks/image-vision.ts +127 -5
- package/hooks/mcp.ts +10 -2
- package/hooks/pi-tool-filter.ts +2 -6
- package/hooks/skeleton.ts +4 -1
- package/hooks/smart-at.ts +18 -23
- package/hooks/thinking-label-strip.ts +79 -0
- package/index.ts +207 -176
- package/package.json +3 -1
- package/skills/pi-docs/SKILL.md +3 -4
- package/tools/ask/index.ts +1 -1
- package/tools/patch/core.ts +154 -81
- package/tools/patch/encoding.ts +172 -0
- package/tools/patch/index.ts +11 -7
- package/ui/ask.ts +71 -31
package/ui/ask.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
21
|
-
import { Container, Spacer, Text, type TUI, type Component, getKeybindings } from "@earendil-works/pi-tui";
|
|
21
|
+
import { Container, Spacer, Text, matchesKey, Key, type TUI, type Component, getKeybindings } from "@earendil-works/pi-tui";
|
|
22
22
|
|
|
23
23
|
export type AskQuestionType = "text" | "single" | "multi";
|
|
24
24
|
|
|
@@ -37,25 +37,30 @@ export interface AskAnswer {
|
|
|
37
37
|
|
|
38
38
|
interface QuestionState {
|
|
39
39
|
value: string | string[];
|
|
40
|
+
/** Cursor position within value for text type (0 = before first char). */
|
|
41
|
+
textCursor: number;
|
|
40
42
|
cursor: number; // option cursor; opts.length maps to the "Other" row
|
|
41
43
|
/** Typed text when the cursor sits on the "Other" row (single or multi). */
|
|
42
44
|
customText: string;
|
|
45
|
+
/** Cursor position within customText (0 = before first char). */
|
|
46
|
+
customCursor: number;
|
|
43
47
|
/** Multi only: whether "Other" is currently toggled into the selection. */
|
|
44
48
|
customSelected: boolean;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
function parseDefault(type: AskQuestionType, options: string[] | undefined, defaultValue: string | undefined): QuestionState {
|
|
48
52
|
if (type === "text") {
|
|
49
|
-
|
|
53
|
+
const v = defaultValue ?? "";
|
|
54
|
+
return { value: v, textCursor: v.length, cursor: 0, customText: "", customCursor: 0, customSelected: false };
|
|
50
55
|
}
|
|
51
56
|
const opts = options ?? [];
|
|
52
57
|
if (type === "single") {
|
|
53
58
|
const idx = defaultValue ? Math.max(0, opts.indexOf(defaultValue)) : 0;
|
|
54
|
-
return { value: opts[idx] ?? "", cursor: idx, customText: "", customSelected: false };
|
|
59
|
+
return { value: opts[idx] ?? "", textCursor: 0, cursor: idx, customText: "", customCursor: 0, customSelected: false };
|
|
55
60
|
}
|
|
56
61
|
// multi: comma-separated default, or empty
|
|
57
62
|
const selected = defaultValue ? defaultValue.split(",").map(s => s.trim()).filter(Boolean) : [];
|
|
58
|
-
return { value: selected, cursor: 0, customText: "", customSelected: false };
|
|
63
|
+
return { value: selected, textCursor: 0, cursor: 0, customText: "", customCursor: 0, customSelected: false };
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
/** Returns true for single/multi choice questions, which always get an
|
|
@@ -307,13 +312,23 @@ export class AskComponent extends Container {
|
|
|
307
312
|
const state = this.currentState();
|
|
308
313
|
|
|
309
314
|
if (q.type === "text") {
|
|
315
|
+
const tc = state.textCursor;
|
|
316
|
+
const val = state.value as string;
|
|
310
317
|
if (data === "\x7f" || data === "backspace") {
|
|
311
|
-
|
|
318
|
+
if (tc > 0) {
|
|
319
|
+
state.value = val.slice(0, tc - 1) + val.slice(tc);
|
|
320
|
+
state.textCursor--;
|
|
321
|
+
}
|
|
322
|
+
} else if (matchesKey(data, Key.left)) {
|
|
323
|
+
if (tc > 0) state.textCursor--;
|
|
324
|
+
} else if (matchesKey(data, Key.right)) {
|
|
325
|
+
if (tc < val.length) state.textCursor++;
|
|
312
326
|
} else if (data.length > 0 && data.charCodeAt(0) >= 0x20) {
|
|
313
327
|
// Accept any printable character: ASCII, Latin-1, CJK, emoji
|
|
314
328
|
// surrogate pairs. The earlier `>= " " && <= "~"` filter
|
|
315
329
|
// rejected every code point above U+007F (i.e. all Chinese).
|
|
316
|
-
state.value = (
|
|
330
|
+
state.value = val.slice(0, tc) + data + val.slice(tc);
|
|
331
|
+
state.textCursor += data.length;
|
|
317
332
|
}
|
|
318
333
|
} else if (q.type === "single" && q.options) {
|
|
319
334
|
const opts = q.options;
|
|
@@ -328,18 +343,26 @@ export class AskComponent extends Container {
|
|
|
328
343
|
const next = (state.cursor + 1) % totalLen;
|
|
329
344
|
state.cursor = next;
|
|
330
345
|
state.value = next < opts.length ? opts[next] : "";
|
|
346
|
+
} else if (onCustomRow) {
|
|
347
|
+
if (data === "\x7f" || data === "backspace") {
|
|
348
|
+
if (state.customCursor > 0) {
|
|
349
|
+
state.customText = state.customText.slice(0, state.customCursor - 1) + state.customText.slice(state.customCursor);
|
|
350
|
+
state.customCursor--;
|
|
351
|
+
}
|
|
352
|
+
} else if (matchesKey(data, Key.left)) {
|
|
353
|
+
if (state.customCursor > 0) state.customCursor--;
|
|
354
|
+
} else if (matchesKey(data, Key.right)) {
|
|
355
|
+
if (state.customCursor < state.customText.length) state.customCursor++;
|
|
356
|
+
} else if (data.length > 0 && data.charCodeAt(0) >= 0x20) {
|
|
357
|
+
state.customText = state.customText.slice(0, state.customCursor) + data + state.customText.slice(state.customCursor);
|
|
358
|
+
state.customCursor += data.length;
|
|
359
|
+
}
|
|
331
360
|
} else if (data >= "1" && data <= "9") {
|
|
332
361
|
const idx = parseInt(data, 10) - 1;
|
|
333
362
|
if (idx < totalLen) {
|
|
334
363
|
state.cursor = idx;
|
|
335
364
|
state.value = idx < opts.length ? opts[idx] : "";
|
|
336
365
|
}
|
|
337
|
-
} else if (onCustomRow) {
|
|
338
|
-
if (data === "\x7f" || data === "backspace") {
|
|
339
|
-
state.customText = state.customText.slice(0, -1);
|
|
340
|
-
} else if (data.length > 0 && data.charCodeAt(0) >= 0x20) {
|
|
341
|
-
state.customText += data;
|
|
342
|
-
}
|
|
343
366
|
}
|
|
344
367
|
} else if (q.type === "multi" && q.options) {
|
|
345
368
|
const opts = q.options;
|
|
@@ -360,6 +383,21 @@ export class AskComponent extends Container {
|
|
|
360
383
|
else selected.add(opt);
|
|
361
384
|
state.value = Array.from(selected);
|
|
362
385
|
}
|
|
386
|
+
} else if (onCustomRow) {
|
|
387
|
+
if (data === "\x7f" || data === "backspace") {
|
|
388
|
+
if (state.customCursor > 0) {
|
|
389
|
+
state.customText = state.customText.slice(0, state.customCursor - 1) + state.customText.slice(state.customCursor);
|
|
390
|
+
state.customCursor--;
|
|
391
|
+
}
|
|
392
|
+
} else if (matchesKey(data, Key.left)) {
|
|
393
|
+
if (state.customCursor > 0) state.customCursor--;
|
|
394
|
+
} else if (matchesKey(data, Key.right)) {
|
|
395
|
+
if (state.customCursor < state.customText.length) state.customCursor++;
|
|
396
|
+
} else if (data.length > 0 && data.charCodeAt(0) >= 0x20) {
|
|
397
|
+
state.customSelected = true;
|
|
398
|
+
state.customText = state.customText.slice(0, state.customCursor) + data + state.customText.slice(state.customCursor);
|
|
399
|
+
state.customCursor += data.length;
|
|
400
|
+
}
|
|
363
401
|
} else if (data >= "1" && data <= "9") {
|
|
364
402
|
const idx = parseInt(data, 10) - 1;
|
|
365
403
|
if (idx < totalLen) {
|
|
@@ -372,15 +410,6 @@ export class AskComponent extends Container {
|
|
|
372
410
|
state.value = Array.from(selected);
|
|
373
411
|
}
|
|
374
412
|
}
|
|
375
|
-
} else if (onCustomRow && data.length > 0 && data.charCodeAt(0) >= 0x20) {
|
|
376
|
-
// Typing on the "Other" row auto-selects it and feeds customText.
|
|
377
|
-
// Backspace never enters this branch (handled below).
|
|
378
|
-
if (data !== "\x7f" && data !== "backspace") {
|
|
379
|
-
state.customSelected = true;
|
|
380
|
-
state.customText += data;
|
|
381
|
-
} else {
|
|
382
|
-
state.customText = state.customText.slice(0, -1);
|
|
383
|
-
}
|
|
384
413
|
}
|
|
385
414
|
}
|
|
386
415
|
|
|
@@ -415,8 +444,11 @@ export class AskComponent extends Container {
|
|
|
415
444
|
|
|
416
445
|
if (q.type === "text") {
|
|
417
446
|
const value = state.value as string;
|
|
418
|
-
const
|
|
419
|
-
|
|
447
|
+
const tc = state.textCursor;
|
|
448
|
+
const before = value.slice(0, tc);
|
|
449
|
+
const after = value.slice(tc);
|
|
450
|
+
const cursor = this.theme.fg("accent", "|");
|
|
451
|
+
lines.push(`${before}${cursor}${after}`);
|
|
420
452
|
} else if (q.type === "single" && q.options) {
|
|
421
453
|
const opts = q.options;
|
|
422
454
|
const totalLen = effectiveOptionCount(q);
|
|
@@ -432,12 +464,16 @@ export class AskComponent extends Container {
|
|
|
432
464
|
let line: string;
|
|
433
465
|
if (isCustomRow) {
|
|
434
466
|
const text = state.customText;
|
|
435
|
-
const
|
|
436
|
-
|
|
467
|
+
const cc = state.customCursor;
|
|
468
|
+
const before = text.slice(0, cc);
|
|
469
|
+
const after = text.slice(cc);
|
|
470
|
+
const cmark = atCursor ? this.theme.fg("accent", "|") : "";
|
|
471
|
+
const hl = atCursor ? this.theme.fg("accent", ` ${icon} ${optLabel}: `) : ` ${icon} ${optLabel}: `;
|
|
472
|
+
line = `${hl}${before}${cmark}${after}`;
|
|
437
473
|
} else {
|
|
438
|
-
line = ` ${icon} ${optLabel}`;
|
|
474
|
+
line = atCursor ? this.theme.fg("accent", ` ${icon} ${optLabel}`) : ` ${icon} ${optLabel}`;
|
|
439
475
|
}
|
|
440
|
-
lines.push(
|
|
476
|
+
lines.push(line);
|
|
441
477
|
}
|
|
442
478
|
} else if (q.type === "multi" && q.options) {
|
|
443
479
|
const opts = q.options;
|
|
@@ -452,12 +488,16 @@ export class AskComponent extends Container {
|
|
|
452
488
|
let line: string;
|
|
453
489
|
if (isCustomRow) {
|
|
454
490
|
const text = state.customText;
|
|
455
|
-
const
|
|
456
|
-
|
|
491
|
+
const cc = state.customCursor;
|
|
492
|
+
const before = text.slice(0, cc);
|
|
493
|
+
const after = text.slice(cc);
|
|
494
|
+
const cmark = atCursor ? this.theme.fg("accent", "|") : "";
|
|
495
|
+
const hl = atCursor ? this.theme.fg("accent", ` ${icon} ${optLabel}: `) : ` ${icon} ${optLabel}: `;
|
|
496
|
+
line = `${hl}${before}${cmark}${after}`;
|
|
457
497
|
} else {
|
|
458
|
-
line = ` ${icon} ${optLabel}`;
|
|
498
|
+
line = atCursor ? this.theme.fg("accent", ` ${icon} ${optLabel}`) : ` ${icon} ${optLabel}`;
|
|
459
499
|
}
|
|
460
|
-
lines.push(
|
|
500
|
+
lines.push(line);
|
|
461
501
|
}
|
|
462
502
|
}
|
|
463
503
|
|