@xynogen/pix-ask 0.2.1 → 0.2.3
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/package.json +1 -1
- package/src/frame.ts +72 -5
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Pi tool — structured questionnaire UI (`ask_user`).
|
|
|
4
4
|
|
|
5
5
|
## What it does
|
|
6
6
|
|
|
7
|
-
Registers the `ask_user` tool in Pi. When the agent needs to resolve ambiguous requirements, it presents up to 4 structured multiple-choice questions in a TUI dialog. Each question requires 2–4 options with labels and descriptions; supports multi-select and markdown side-by-side previews for richer context. Single-select questions auto-append a "Type something." row for free-form input
|
|
7
|
+
Registers the `ask_user` tool in Pi. When the agent needs to resolve ambiguous requirements, it presents up to 4 structured multiple-choice questions in a TUI dialog. Each question requires 2–4 options with labels and descriptions; supports multi-select and markdown side-by-side previews for richer context. Single-select questions (without a preview) auto-append a "Type something." row for free-form input; multi-select questions append a "Next" row to advance. Single-select questions with a `preview` skip the free-form row to make room for the side-by-side layout. In non-interactive (RPC/JSON) mode, falls back to text-based prompts. The agent uses this tool before proceeding rather than guessing at intent.
|
|
8
8
|
|
|
9
9
|
## Install
|
|
10
10
|
|
package/package.json
CHANGED
package/src/frame.ts
CHANGED
|
@@ -1,6 +1,73 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Modal frame primitives — inlined from pix-pretty/modal-frame to avoid
|
|
3
|
+
* cross-package subpath imports that break under Node CJS require.
|
|
4
|
+
*/
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
7
|
+
|
|
8
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
const MIN_WIDTH = 40;
|
|
11
|
+
const MAX_WIDTH = 96;
|
|
12
|
+
const MARGIN = 4;
|
|
13
|
+
/** 2 border cols + 2 padding spaces */
|
|
14
|
+
const CHROME = 4;
|
|
15
|
+
|
|
16
|
+
// ── Width ─────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/** Clamp terminal width to a sane modal width (40–96 cols). */
|
|
19
|
+
export function modalWidth(termWidth: number): number {
|
|
20
|
+
return Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, termWidth - MARGIN));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Frame ─────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export interface FrameOptions {
|
|
26
|
+
width: number;
|
|
27
|
+
lines: string[];
|
|
28
|
+
/** Color function for border glyphs — e.g. `(s) => theme.fg("accent", s)` */
|
|
29
|
+
color: (s: string) => string;
|
|
30
|
+
/** Background fill function — e.g. `(s) => theme.bg("customMessageBg", s)` */
|
|
31
|
+
bg?: (s: string) => string;
|
|
32
|
+
/** Optional pre-styled string rendered as the first content row (tab bar etc.) */
|
|
33
|
+
top?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Render a rounded modal box.
|
|
38
|
+
*
|
|
39
|
+
* Returns an array of full-width ANSI strings:
|
|
40
|
+
* ╭──────────────────╮
|
|
41
|
+
* │ [top row] │ ← only if top is set
|
|
42
|
+
* │ content line 1 │
|
|
43
|
+
* │ content line 2 │
|
|
44
|
+
* ╰──────────────────╯
|
|
45
|
+
*/
|
|
46
|
+
export function frameLines(opts: FrameOptions): string[] {
|
|
47
|
+
const { width, lines, color, top } = opts;
|
|
48
|
+
const bg = opts.bg ?? ((s: string) => s);
|
|
49
|
+
const inner = Math.max(1, width - CHROME);
|
|
50
|
+
const dashes = "─".repeat(width - 2);
|
|
51
|
+
|
|
52
|
+
const SENTINEL = "\x00";
|
|
53
|
+
const bgOpen = bg(SENTINEL).split(SENTINEL)[0] ?? "";
|
|
54
|
+
const reassert = (s: string): string =>
|
|
55
|
+
bgOpen
|
|
56
|
+
? s.replace(/\x1b\[([0-9;]*)m/g, (seq, p: string) =>
|
|
57
|
+
p === "0" || p.split(";").includes("49") ? `${seq}${bgOpen}` : seq,
|
|
58
|
+
)
|
|
59
|
+
: s;
|
|
60
|
+
|
|
61
|
+
const row = (content: string): string => {
|
|
62
|
+
const pad = inner - visibleWidth(content);
|
|
63
|
+
const padded =
|
|
64
|
+
pad > 0 ? content + " ".repeat(pad) : truncateToWidth(content, inner);
|
|
65
|
+
return bg(`${color("│")} ${reassert(padded)} ${color("│")}`);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const out: string[] = [bg(color(`╭${dashes}╮`))];
|
|
69
|
+
if (top !== undefined) out.push(row(top));
|
|
70
|
+
for (const line of lines) out.push(row(line));
|
|
71
|
+
out.push(bg(color(`╰${dashes}╯`)));
|
|
72
|
+
return out;
|
|
73
|
+
}
|