pi-ask-user 0.5.0 → 0.5.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.
Files changed (2) hide show
  1. package/index.ts +57 -37
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -27,7 +27,11 @@ import {
27
27
  truncateToWidth,
28
28
  wrapTextWithAnsi,
29
29
  } from "@mariozechner/pi-tui";
30
- import { renderSingleSelectRows, type QuestionOption } from "./single-select-layout";
30
+ import { renderSingleSelectRows } from "./single-select-layout";
31
+
32
+ import { createRequire } from "node:module";
33
+ const _require = createRequire(import.meta.url);
34
+ const ASK_USER_VERSION: string = (_require("./package.json") as { version: string }).version;
31
35
 
32
36
  type AskOptionInput = QuestionOption | string;
33
37
 
@@ -124,13 +128,25 @@ class BoxBorderTop implements Component {
124
128
 
125
129
  class BoxBorderBottom implements Component {
126
130
  private color: (s: string) => string;
127
- constructor(color: (s: string) => string) {
131
+ private label?: string;
132
+ private labelColor?: (s: string) => string;
133
+ constructor(color: (s: string) => string, label?: string, labelColor?: (s: string) => string) {
128
134
  this.color = color;
135
+ this.label = label;
136
+ this.labelColor = labelColor;
129
137
  }
130
138
  invalidate(): void {}
131
139
  render(width: number): string[] {
132
140
  const inner = Math.max(0, width - 2);
133
- return [this.color(`╰${"─".repeat(inner)}╯`)];
141
+ if (!this.label || inner < this.label.length + 4) {
142
+ return [this.color(`╰${"─".repeat(inner)}╯`)];
143
+ }
144
+ const tag = ` ${this.label} `;
145
+ const leftDashes = inner - tag.length - 1;
146
+ const style = this.labelColor ?? this.color;
147
+ return [
148
+ this.color("╰" + "─".repeat(Math.max(0, leftDashes))) + style(tag) + this.color("─╯"),
149
+ ];
134
150
  }
135
151
  }
136
152
 
@@ -481,53 +497,53 @@ class WrappedSingleSelectList implements Component {
481
497
  private buildPreviewLines(width: number, filteredOptions: QuestionOption[], maxLines: number): string[] {
482
498
  if (maxLines <= 0) return [];
483
499
 
484
- const lines: string[] = [];
485
- const pushWrapped = (text: string, style: (line: string) => string): void => {
486
- for (const line of wrapTextWithAnsi(text, Math.max(10, width))) {
487
- lines.push(truncateToWidth(style(line), width, ""));
488
- }
489
- };
490
- const pushBlank = (): void => {
491
- if (lines.length === 0 || lines[lines.length - 1] !== "") {
492
- lines.push("");
493
- }
494
- };
500
+ let mdTheme: MarkdownTheme | undefined;
501
+ try {
502
+ mdTheme = getMarkdownTheme();
503
+ } catch {}
495
504
 
496
- pushWrapped("Details", (line) => this.theme.fg("accent", this.theme.bold(line)));
497
- pushBlank();
505
+ // Build a markdown string for the preview content
506
+ let md = "";
498
507
 
499
508
  if (this.isFreeformRow(this.selectedIndex, filteredOptions)) {
500
- pushWrapped("Custom response", (line) => this.theme.fg("text", this.theme.bold(line)));
501
- pushBlank();
502
- pushWrapped("Open the editor to write any answer.", (line) => this.theme.fg("text", line));
503
- pushBlank();
504
- pushWrapped("Use this when none of the listed options fit.", (line) => this.theme.fg("muted", line));
509
+ md += "## Custom response\n\n";
510
+ md += "Open the editor to write **any** answer.\n\n";
511
+ md += "*Use this when none of the listed options fit.*\n";
505
512
  if (this.searchQuery) {
506
- pushBlank();
507
- pushWrapped(`Current filter: ${this.searchQuery}`, (line) => this.theme.fg("dim", line));
513
+ md += `\n> Current filter: \`${this.searchQuery}\`\n`;
508
514
  }
509
515
  } else {
510
516
  const selected = filteredOptions[this.selectedIndex];
511
517
  if (!selected) {
512
- pushWrapped("No option selected", (line) => this.theme.fg("muted", line));
518
+ md += "*No option selected*\n";
513
519
  } else {
514
- pushWrapped(selected.title, (line) => this.theme.fg("text", this.theme.bold(line)));
515
- pushBlank();
520
+ md += `## ${selected.title}\n\n`;
516
521
  if (selected.description?.trim()) {
517
- pushWrapped(selected.description, (line) => this.theme.fg("text", line));
522
+ md += `${selected.description}\n`;
518
523
  } else {
519
- pushWrapped("No additional details provided for this option.", (line) => this.theme.fg("muted", line));
524
+ md += "*No additional details provided for this option.*\n";
520
525
  }
521
- pushBlank();
522
- pushWrapped("Press Enter to select this option.", (line) => this.theme.fg("dim", line));
526
+ md += `\n---\n\nPress \`Enter\` to select this option.\n`;
523
527
  if (this.searchQuery) {
524
- pushBlank();
525
- pushWrapped(`Filter: ${this.searchQuery}`, (line) => this.theme.fg("dim", line));
528
+ md += `\n> Filter: \`${this.searchQuery}\`\n`;
526
529
  }
527
530
  }
528
531
  }
529
532
 
530
- while (lines.length > 0 && lines[lines.length - 1] === "") {
533
+ // Render via Markdown component if theme is available, otherwise fall back to plain text
534
+ let lines: string[];
535
+ if (mdTheme) {
536
+ const mdComponent = new Markdown(md.trim(), 0, 0, mdTheme);
537
+ lines = mdComponent.render(width);
538
+ } else {
539
+ lines = [];
540
+ for (const line of wrapTextWithAnsi(md.trim(), Math.max(10, width))) {
541
+ lines.push(truncateToWidth(line, width, ""));
542
+ }
543
+ }
544
+
545
+ // Trim trailing blanks
546
+ while (lines.length > 0 && lines[lines.length - 1]?.trim() === "") {
531
547
  lines.pop();
532
548
  }
533
549
 
@@ -733,7 +749,11 @@ class AskComponent extends Container {
733
749
  this.addChild(this.helpText);
734
750
 
735
751
  this.addChild(new Spacer(1));
736
- this.addChild(new BoxBorderBottom((s: string) => theme.fg("accent", s)));
752
+ this.addChild(new BoxBorderBottom(
753
+ (s: string) => theme.fg("accent", s),
754
+ `v${ASK_USER_VERSION}`,
755
+ (s: string) => theme.fg("dim", s),
756
+ ));
737
757
 
738
758
  this.updateStaticText();
739
759
  this.showSelectMode();
@@ -766,7 +786,7 @@ class AskComponent extends Container {
766
786
  if (index === 0 || index === rawLines.length - 1) {
767
787
  // Box top/bottom borders already rendered at innerWidth — re-render at full width
768
788
  if (index === 0) return new BoxBorderTop(borderColor, "ask_user", titleColor).render(width)[0];
769
- return new BoxBorderBottom(borderColor).render(width)[0];
789
+ return new BoxBorderBottom(borderColor, `v${ASK_USER_VERSION}`, (s: string) => this.theme.fg("dim", s)).render(width)[0];
770
790
  }
771
791
  const padded = truncateToWidth(line, innerWidth, "", true);
772
792
  return `${borderColor(BOX_BORDER_LEFT)}${padded}${borderColor(BOX_BORDER_RIGHT)}`;
@@ -951,11 +971,11 @@ export default function (pi: ExtensionAPI) {
951
971
  name: "ask_user",
952
972
  label: "Ask User",
953
973
  description:
954
- "Ask the user a question with optional multiple-choice answers. Use this to gather information interactively. Before calling, gather context with tools (read/exa/ref) and pass a short summary via the context field.",
974
+ "Ask the user a question with optional multiple-choice answers. Use this to gather information interactively. Before calling, gather context with tools (read/web/ref) and pass a short summary via the context field.",
955
975
  promptSnippet:
956
976
  "Ask the user a question with optional multiple-choice answers to gather information interactively",
957
977
  promptGuidelines: [
958
- "Before calling ask_user, gather context with tools (read/exa/ref) and pass a short summary via the context field.",
978
+ "Before calling ask_user, gather context with tools (read/web/ref) and pass a short summary via the context field.",
959
979
  "Use ask_user when the user's intent is ambiguous, when a decision requires explicit user input, or when multiple valid options exist.",
960
980
  ],
961
981
  parameters: Type.Object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ask-user",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Interactive ask_user tool for pi-coding-agent with searchable split-pane selection UI, multi-select, and freeform input",
5
5
  "type": "module",
6
6
  "keywords": [