@hoilab/ada-cli 0.84.6 → 0.84.8

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.
@@ -0,0 +1,272 @@
1
+ import { Container, Input, getKeybindings, truncateToWidth } from "@earendil-works/pi-tui";
2
+ import { checkForNewPiVersion } from "../../../utils/version-check.js";
3
+ import { theme } from "../theme/theme.js";
4
+ import { filterAndSortSessions } from "./session-selector-search.js";
5
+ const ASCII_LOGO = [
6
+ " █████╗ ██████╗ █████╗",
7
+ " ██╔══██╗██╔══██╗██╔══██╗",
8
+ " ███████║██║ ██║███████║",
9
+ " ██╔══██║██║ ██║██╔══██║",
10
+ " ██║ ██║██████╔╝██║ ██║",
11
+ " ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝",
12
+ ].join("\n");
13
+ function sessionTitle(session) {
14
+ if (session.name)
15
+ return session.name;
16
+ const first = (session.firstMessage ?? "").replace(/\s+/g, " ").trim();
17
+ if (first.length > 0)
18
+ return first.length > 60 ? `${first.slice(0, 60)}…` : first;
19
+ return "Conversation";
20
+ }
21
+ function relativeTime(iso) {
22
+ const diffMs = Date.now() - new Date(iso).getTime();
23
+ const mins = Math.floor(diffMs / 60000);
24
+ if (mins < 1)
25
+ return "now";
26
+ if (mins < 60)
27
+ return `${mins}m`;
28
+ const hours = Math.floor(mins / 60);
29
+ if (hours < 24)
30
+ return `${hours}h`;
31
+ const days = Math.floor(hours / 24);
32
+ if (days < 7)
33
+ return `${days}d`;
34
+ if (days < 30)
35
+ return `${Math.floor(days / 7)}w`;
36
+ return `${Math.floor(days / 30)}mo`;
37
+ }
38
+ /**
39
+ * Central welcome flow (opencode style) shown when the CLI starts:
40
+ * 1) entry screen: "Enter as guest" (login is disabled for now)
41
+ * 2) home screen: welcome banner, update notice, recent conversations and
42
+ * a "New conversation" action.
43
+ */
44
+ export class HomeScreenComponent extends Container {
45
+ phase = "login";
46
+ sessions = [];
47
+ filtered = [];
48
+ selectedIndex = 0; // 0 = "New conversation", then sessions
49
+ query = "";
50
+ loading = false;
51
+ updateAvailable = null;
52
+ updateChecked = false;
53
+ searchInput;
54
+ loadSessions;
55
+ version;
56
+ _focused = false;
57
+ requestRenderFn = () => { };
58
+ onNew;
59
+ onResume;
60
+ onExit;
61
+ onUpdate;
62
+ constructor(loadSessions, version) {
63
+ super();
64
+ this.loadSessions = loadSessions;
65
+ this.version = version;
66
+ this.searchInput = new Input();
67
+ this.searchInput.onSubmit = () => this.confirmSelection();
68
+ }
69
+ // Focusable
70
+ get focused() {
71
+ return this._focused;
72
+ }
73
+ set focused(value) {
74
+ this._focused = value;
75
+ this.searchInput.focused = value;
76
+ }
77
+ async reload() {
78
+ this.loading = true;
79
+ this.requestRenderFn();
80
+ try {
81
+ this.sessions = await this.loadSessions();
82
+ }
83
+ catch {
84
+ this.sessions = [];
85
+ }
86
+ this.loading = false;
87
+ this.applyFilter();
88
+ this.requestRenderFn();
89
+ }
90
+ applyFilter() {
91
+ const q = this.query.trim();
92
+ this.filtered = q
93
+ ? filterAndSortSessions(this.sessions, q, "recent", "all")
94
+ : [...this.sessions].sort((a, b) => b.modified.getTime() - a.modified.getTime());
95
+ this.selectedIndex = Math.min(this.selectedIndex, this.filtered.length);
96
+ }
97
+ confirmSelection() {
98
+ if (this.phase === "login") {
99
+ this.enterHome();
100
+ return;
101
+ }
102
+ if (this.selectedIndex === 0) {
103
+ this.onNew?.();
104
+ }
105
+ else {
106
+ const session = this.filtered[this.selectedIndex - 1];
107
+ if (session)
108
+ this.onResume?.(session.path);
109
+ }
110
+ }
111
+ enterHome() {
112
+ this.phase = "home";
113
+ this.selectedIndex = 0;
114
+ void this.reload();
115
+ void this.checkUpdate();
116
+ }
117
+ async checkUpdate() {
118
+ if (this.updateChecked)
119
+ return;
120
+ this.updateChecked = true;
121
+ try {
122
+ const release = await checkForNewPiVersion(this.version);
123
+ if (release) {
124
+ this.updateAvailable = release.version;
125
+ this.requestRenderFn();
126
+ }
127
+ }
128
+ catch {
129
+ // Offline or unreachable — ignore silently.
130
+ }
131
+ }
132
+ handleInput(data) {
133
+ const kb = getKeybindings();
134
+ if (this.phase === "login") {
135
+ if (kb.matches(data, "tui.select.confirm") || data === "\r" || data === "\n") {
136
+ this.enterHome();
137
+ }
138
+ else if (kb.matches(data, "tui.select.cancel") || data === "q" || data === "Q") {
139
+ this.onExit?.();
140
+ }
141
+ else if (kb.matches(data, "tui.select.down")) {
142
+ // Only one actionable option on the login screen; ignore.
143
+ }
144
+ return;
145
+ }
146
+ if (kb.matches(data, "tui.select.up")) {
147
+ this.selectedIndex = Math.max(0, this.selectedIndex - 1);
148
+ this.requestRenderFn();
149
+ return;
150
+ }
151
+ if (kb.matches(data, "tui.select.down")) {
152
+ this.selectedIndex = Math.min(this.filtered.length, this.selectedIndex + 1);
153
+ this.requestRenderFn();
154
+ return;
155
+ }
156
+ if (kb.matches(data, "tui.select.confirm") || data === "\r" || data === "\n") {
157
+ this.confirmSelection();
158
+ return;
159
+ }
160
+ if (kb.matches(data, "tui.select.cancel")) {
161
+ this.onExit?.();
162
+ return;
163
+ }
164
+ if (data === "n" || data === "N") {
165
+ this.onNew?.();
166
+ return;
167
+ }
168
+ if (data === "u" || data === "U") {
169
+ if (this.updateAvailable) {
170
+ this.onUpdate?.();
171
+ return;
172
+ }
173
+ }
174
+ if (data === "q" || data === "Q") {
175
+ this.onExit?.();
176
+ return;
177
+ }
178
+ // Anything else goes to the search input (filters sessions)
179
+ this.searchInput.handleInput(data);
180
+ this.query = this.searchInput.getValue();
181
+ this.applyFilter();
182
+ this.requestRenderFn();
183
+ }
184
+ invalidate() { }
185
+ setRequestRender(fn) {
186
+ this.requestRenderFn = fn;
187
+ }
188
+ render(width) {
189
+ const lines = [];
190
+ const center = (text) => {
191
+ const pad = Math.max(0, Math.floor((width - text.length) / 2));
192
+ return " ".repeat(pad) + text;
193
+ };
194
+ const dim = (t) => theme.fg("dim", t);
195
+ const muted = (t) => theme.fg("muted", t);
196
+ const accent = (t) => theme.fg("accent", t);
197
+ const warning = (t) => theme.fg("warning", t);
198
+ lines.push("");
199
+ lines.push(center(accent(ASCII_LOGO)));
200
+ lines.push(center(accent("Ada") + dim(" · coding agent · HOILAB TECH") + muted(` v${this.version}`)));
201
+ lines.push("");
202
+ if (this.phase === "login") {
203
+ // ---- Entry screen -------------------------------------------
204
+ lines.push("");
205
+ lines.push(center(dim("Bienvenido a Ada — el agente de codificación de HOILAB TECH")));
206
+ lines.push("");
207
+ lines.push(center(accent("▸ ") + theme.bold("Entrar como invitado")));
208
+ lines.push(center(muted(" Loguearme") + dim(" (próximamente)")));
209
+ lines.push("");
210
+ lines.push("");
211
+ lines.push(center(muted(["Enter para continuar", "q para salir"].join(" · "))));
212
+ return lines;
213
+ }
214
+ // ---- Home screen ------------------------------------------------
215
+ if (this.updateAvailable) {
216
+ lines.push(center(warning(`⚠ Nueva versión disponible: v${this.updateAvailable} ` +
217
+ theme.fg("accent", "[u] actualizar") +
218
+ dim(" · pulsa q para ignorar"))));
219
+ lines.push("");
220
+ }
221
+ else if (this.updateChecked) {
222
+ lines.push(center(dim("✓ Sistema actualizado")));
223
+ lines.push("");
224
+ }
225
+ lines.push(center(dim("Elige una conversación o empieza una nueva:")));
226
+ lines.push("");
227
+ // "New conversation" option
228
+ const isNew = this.selectedIndex === 0;
229
+ const newLine = `${isNew ? accent("▸ ") : " "}${accent("✦")} ${isNew ? theme.bold("Nueva conversación") : "Nueva conversación"}`;
230
+ lines.push(center(newLine));
231
+ lines.push(center(dim("─".repeat(Math.min(46, width - 4)))));
232
+ lines.push("");
233
+ if (this.loading) {
234
+ lines.push(center(dim("Cargando conversaciones…")));
235
+ }
236
+ else if (this.filtered.length === 0) {
237
+ lines.push(center(muted(this.query
238
+ ? "No hay conversaciones que coincidan con tu búsqueda"
239
+ : "Todavía no hay conversaciones anteriores")));
240
+ }
241
+ else {
242
+ const maxVisible = Math.max(1, Math.min(12, Math.floor((width * 2) / 3)));
243
+ const visible = this.filtered.slice(0, maxVisible);
244
+ for (let i = 0; i < visible.length; i++) {
245
+ const session = visible[i];
246
+ const isSelected = this.selectedIndex === i + 1;
247
+ const title = sessionTitle(session);
248
+ const meta = `${relativeTime(session.modified.toISOString())} · ${session.messageCount} msgs`;
249
+ const arrow = isSelected ? "▸ " : " ";
250
+ const titleStyled = isSelected ? theme.bold(accent(title)) : title;
251
+ lines.push(center(`${arrow}${titleStyled} ${muted(meta)}`));
252
+ }
253
+ if (this.filtered.length > maxVisible) {
254
+ lines.push(center(dim(`… y ${this.filtered.length - maxVisible} más`)));
255
+ }
256
+ }
257
+ lines.push("");
258
+ lines.push(center(dim(`Buscar: ${this.query}${this.searchInput.focused ? "▍" : ""}`)));
259
+ lines.push("");
260
+ const hints = [
261
+ "↑/↓ navegar",
262
+ "Enter seleccionar",
263
+ "n nueva",
264
+ this.updateAvailable ? "u actualizar" : null,
265
+ "escribir para buscar",
266
+ "q salir",
267
+ ].filter((h) => h !== null);
268
+ lines.push(center(muted(hints.join(" · "))));
269
+ return lines.map((line) => truncateToWidth(line, width));
270
+ }
271
+ }
272
+ //# sourceMappingURL=home-screen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"home-screen.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/home-screen.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Component, Container, Input, getKeybindings, truncateToWidth } from \"@earendil-works/pi-tui\";\nimport type { SessionInfo, SessionListProgress } from \"../../../core/session-manager.ts\";\nimport { checkForNewPiVersion } from \"../../../utils/version-check.ts\";\nimport { theme } from \"../theme/theme.ts\";\nimport { filterAndSortSessions } from \"./session-selector-search.ts\";\n\nexport type HomeScreenResult = { kind: \"new\" } | { kind: \"resume\"; path: string } | null;\n\ntype SessionsLoader = (onProgress?: SessionListProgress) => Promise<SessionInfo[]>;\n\nconst ASCII_LOGO = [\n\t\" █████╗ ██████╗ █████╗\",\n\t\" ██╔══██╗██╔══██╗██╔══██╗\",\n\t\" ███████║██║ ██║███████║\",\n\t\" ██╔══██║██║ ██║██╔══██║\",\n\t\" ██║ ██║██████╔╝██║ ██║\",\n\t\" ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝\",\n].join(\"\\n\");\n\nfunction sessionTitle(session: SessionInfo): string {\n\tif (session.name) return session.name;\n\tconst first = (session.firstMessage ?? \"\").replace(/\\s+/g, \" \").trim();\n\tif (first.length > 0) return first.length > 60 ? `${first.slice(0, 60)}…` : first;\n\treturn \"Conversation\";\n}\n\nfunction relativeTime(iso: string): string {\n\tconst diffMs = Date.now() - new Date(iso).getTime();\n\tconst mins = Math.floor(diffMs / 60000);\n\tif (mins < 1) return \"now\";\n\tif (mins < 60) return `${mins}m`;\n\tconst hours = Math.floor(mins / 60);\n\tif (hours < 24) return `${hours}h`;\n\tconst days = Math.floor(hours / 24);\n\tif (days < 7) return `${days}d`;\n\tif (days < 30) return `${Math.floor(days / 7)}w`;\n\treturn `${Math.floor(days / 30)}mo`;\n}\n\n/**\n * Central welcome flow (opencode style) shown when the CLI starts:\n * 1) entry screen: \"Enter as guest\" (login is disabled for now)\n * 2) home screen: welcome banner, update notice, recent conversations and\n * a \"New conversation\" action.\n */\nexport class HomeScreenComponent extends Container implements Component {\n\tprivate phase: \"login\" | \"home\" = \"login\";\n\tprivate sessions: SessionInfo[] = [];\n\tprivate filtered: SessionInfo[] = [];\n\tprivate selectedIndex = 0; // 0 = \"New conversation\", then sessions\n\tprivate query = \"\";\n\tprivate loading = false;\n\tprivate updateAvailable: string | null = null;\n\tprivate updateChecked = false;\n\tprivate searchInput: Input;\n\tprivate loadSessions: SessionsLoader;\n\tprivate version: string;\n\tprivate _focused = false;\n\tprivate requestRenderFn: () => void = () => {};\n\n\tpublic onNew?: () => void;\n\tpublic onResume?: (path: string) => void;\n\tpublic onExit?: () => void;\n\tpublic onUpdate?: () => void;\n\n\tconstructor(loadSessions: SessionsLoader, version: string) {\n\t\tsuper();\n\t\tthis.loadSessions = loadSessions;\n\t\tthis.version = version;\n\t\tthis.searchInput = new Input();\n\t\tthis.searchInput.onSubmit = () => this.confirmSelection();\n\t}\n\n\t// Focusable\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\n\tasync reload(): Promise<void> {\n\t\tthis.loading = true;\n\t\tthis.requestRenderFn();\n\t\ttry {\n\t\t\tthis.sessions = await this.loadSessions();\n\t\t} catch {\n\t\t\tthis.sessions = [];\n\t\t}\n\t\tthis.loading = false;\n\t\tthis.applyFilter();\n\t\tthis.requestRenderFn();\n\t}\n\n\tprivate applyFilter(): void {\n\t\tconst q = this.query.trim();\n\t\tthis.filtered = q\n\t\t\t? filterAndSortSessions(this.sessions, q, \"recent\", \"all\")\n\t\t\t: [...this.sessions].sort((a, b) => b.modified.getTime() - a.modified.getTime());\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, this.filtered.length);\n\t}\n\n\tprivate confirmSelection(): void {\n\t\tif (this.phase === \"login\") {\n\t\t\tthis.enterHome();\n\t\t\treturn;\n\t\t}\n\t\tif (this.selectedIndex === 0) {\n\t\t\tthis.onNew?.();\n\t\t} else {\n\t\t\tconst session = this.filtered[this.selectedIndex - 1];\n\t\t\tif (session) this.onResume?.(session.path);\n\t\t}\n\t}\n\n\tprivate enterHome(): void {\n\t\tthis.phase = \"home\";\n\t\tthis.selectedIndex = 0;\n\t\tvoid this.reload();\n\t\tvoid this.checkUpdate();\n\t}\n\n\tprivate async checkUpdate(): Promise<void> {\n\t\tif (this.updateChecked) return;\n\t\tthis.updateChecked = true;\n\t\ttry {\n\t\t\tconst release = await checkForNewPiVersion(this.version);\n\t\t\tif (release) {\n\t\t\t\tthis.updateAvailable = release.version;\n\t\t\t\tthis.requestRenderFn();\n\t\t\t}\n\t\t} catch {\n\t\t\t// Offline or unreachable — ignore silently.\n\t\t}\n\t}\n\n\thandleInput(data: string): void {\n\t\tconst kb = getKeybindings();\n\t\tif (this.phase === \"login\") {\n\t\t\tif (kb.matches(data, \"tui.select.confirm\") || data === \"\\r\" || data === \"\\n\") {\n\t\t\t\tthis.enterHome();\n\t\t\t} else if (kb.matches(data, \"tui.select.cancel\") || data === \"q\" || data === \"Q\") {\n\t\t\t\tthis.onExit?.();\n\t\t\t} else if (kb.matches(data, \"tui.select.down\")) {\n\t\t\t\t// Only one actionable option on the login screen; ignore.\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (kb.matches(data, \"tui.select.up\")) {\n\t\t\tthis.selectedIndex = Math.max(0, this.selectedIndex - 1);\n\t\t\tthis.requestRenderFn();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.down\")) {\n\t\t\tthis.selectedIndex = Math.min(this.filtered.length, this.selectedIndex + 1);\n\t\t\tthis.requestRenderFn();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.confirm\") || data === \"\\r\" || data === \"\\n\") {\n\t\t\tthis.confirmSelection();\n\t\t\treturn;\n\t\t}\n\t\tif (kb.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.onExit?.();\n\t\t\treturn;\n\t\t}\n\t\tif (data === \"n\" || data === \"N\") {\n\t\t\tthis.onNew?.();\n\t\t\treturn;\n\t\t}\n\t\tif (data === \"u\" || data === \"U\") {\n\t\t\tif (this.updateAvailable) {\n\t\t\t\tthis.onUpdate?.();\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\tif (data === \"q\" || data === \"Q\") {\n\t\t\tthis.onExit?.();\n\t\t\treturn;\n\t\t}\n\t\t// Anything else goes to the search input (filters sessions)\n\t\tthis.searchInput.handleInput(data);\n\t\tthis.query = this.searchInput.getValue();\n\t\tthis.applyFilter();\n\t\tthis.requestRenderFn();\n\t}\n\n\tinvalidate(): void {}\n\n\tsetRequestRender(fn: () => void): void {\n\t\tthis.requestRenderFn = fn;\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst center = (text: string) => {\n\t\t\tconst pad = Math.max(0, Math.floor((width - text.length) / 2));\n\t\t\treturn \" \".repeat(pad) + text;\n\t\t};\n\t\tconst dim = (t: string) => theme.fg(\"dim\", t);\n\t\tconst muted = (t: string) => theme.fg(\"muted\", t);\n\t\tconst accent = (t: string) => theme.fg(\"accent\", t);\n\t\tconst warning = (t: string) => theme.fg(\"warning\", t);\n\n\t\tlines.push(\"\");\n\t\tlines.push(center(accent(ASCII_LOGO)));\n\t\tlines.push(center(accent(\"Ada\") + dim(\" · coding agent · HOILAB TECH\") + muted(` v${this.version}`)));\n\t\tlines.push(\"\");\n\n\t\tif (this.phase === \"login\") {\n\t\t\t// ---- Entry screen -------------------------------------------\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(center(dim(\"Bienvenido a Ada — el agente de codificación de HOILAB TECH\")));\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(center(accent(\"▸ \") + theme.bold(\"Entrar como invitado\")));\n\t\t\tlines.push(center(muted(\" Loguearme\") + dim(\" (próximamente)\")));\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(\n\t\t\t\tcenter(\n\t\t\t\t\tmuted(\n\t\t\t\t\t\t[\"Enter para continuar\", \"q para salir\"].join(\" · \"),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\treturn lines;\n\t\t}\n\n\t\t// ---- Home screen ------------------------------------------------\n\t\tif (this.updateAvailable) {\n\t\t\tlines.push(\n\t\t\t\tcenter(\n\t\t\t\t\twarning(\n\t\t\t\t\t\t`⚠ Nueva versión disponible: v${this.updateAvailable} ` +\n\t\t\t\t\t\t\ttheme.fg(\"accent\", \"[u] actualizar\") +\n\t\t\t\t\t\t\tdim(\" · pulsa q para ignorar\"),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\tlines.push(\"\");\n\t\t} else if (this.updateChecked) {\n\t\t\tlines.push(center(dim(\"✓ Sistema actualizado\")));\n\t\t\tlines.push(\"\");\n\t\t}\n\n\t\tlines.push(center(dim(\"Elige una conversación o empieza una nueva:\")));\n\t\tlines.push(\"\");\n\n\t\t// \"New conversation\" option\n\t\tconst isNew = this.selectedIndex === 0;\n\t\tconst newLine = `${isNew ? accent(\"▸ \") : \" \"}${accent(\"✦\")} ${\n\t\t\tisNew ? theme.bold(\"Nueva conversación\") : \"Nueva conversación\"\n\t\t}`;\n\t\tlines.push(center(newLine));\n\t\tlines.push(center(dim(\"─\".repeat(Math.min(46, width - 4)))));\n\t\tlines.push(\"\");\n\n\t\tif (this.loading) {\n\t\t\tlines.push(center(dim(\"Cargando conversaciones…\")));\n\t\t} else if (this.filtered.length === 0) {\n\t\t\tlines.push(\n\t\t\t\tcenter(\n\t\t\t\t\tmuted(\n\t\t\t\t\t\tthis.query\n\t\t\t\t\t\t\t? \"No hay conversaciones que coincidan con tu búsqueda\"\n\t\t\t\t\t\t\t: \"Todavía no hay conversaciones anteriores\",\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t} else {\n\t\t\tconst maxVisible = Math.max(1, Math.min(12, Math.floor((width * 2) / 3)));\n\t\t\tconst visible = this.filtered.slice(0, maxVisible);\n\t\t\tfor (let i = 0; i < visible.length; i++) {\n\t\t\t\tconst session = visible[i];\n\t\t\t\tconst isSelected = this.selectedIndex === i + 1;\n\t\t\t\tconst title = sessionTitle(session);\n\t\t\t\tconst meta = `${relativeTime(session.modified.toISOString())} · ${session.messageCount} msgs`;\n\t\t\t\tconst arrow = isSelected ? \"▸ \" : \" \";\n\t\t\t\tconst titleStyled = isSelected ? theme.bold(accent(title)) : title;\n\t\t\t\tlines.push(center(`${arrow}${titleStyled} ${muted(meta)}`));\n\t\t\t}\n\t\t\tif (this.filtered.length > maxVisible) {\n\t\t\t\tlines.push(center(dim(`… y ${this.filtered.length - maxVisible} más`)));\n\t\t\t}\n\t\t}\n\n\t\tlines.push(\"\");\n\t\tlines.push(center(dim(`Buscar: ${this.query}${this.searchInput.focused ? \"▍\" : \"\"}`)));\n\t\tlines.push(\"\");\n\t\tconst hints = [\n\t\t\t\"↑/↓ navegar\",\n\t\t\t\"Enter seleccionar\",\n\t\t\t\"n nueva\",\n\t\t\tthis.updateAvailable ? \"u actualizar\" : null,\n\t\t\t\"escribir para buscar\",\n\t\t\t\"q salir\",\n\t\t].filter((h): h is string => h !== null);\n\t\tlines.push(center(muted(hints.join(\" · \"))));\n\t\treturn lines.map((line) => truncateToWidth(line, width));\n\t}\n}\n"]}