kiku-agent 1.0.9 → 1.0.11

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.
@@ -196,7 +196,7 @@ export function renderJpegFile(filePath, options = {}) {
196
196
  return renderJpegToAnsi(readFileSync(filePath), options);
197
197
  }
198
198
 
199
- function discoverImages(directory) {
199
+ export function discoverImages(directory) {
200
200
  try {
201
201
  return readdirSync(directory, { withFileTypes: true })
202
202
  .filter(entry => entry.isFile() && /\.jpe?g$/i.test(entry.name))
@@ -207,6 +207,89 @@ function discoverImages(directory) {
207
207
  }
208
208
  }
209
209
 
210
+ export function renderTwoImagesSideBySide(filePath1, filePath2, {
211
+ columns = process.stdout.columns || 80,
212
+ rows = process.stdout.rows || 24,
213
+ maxRows = 14,
214
+ } = {}) {
215
+ const availableWidth = Math.max(20, Math.floor(columns));
216
+ const sideBySide = availableWidth >= 76;
217
+ const singleMaxWidth = sideBySide ? Math.min(34, Math.floor((availableWidth - 8) / 2)) : Math.min(50, availableWidth - 4);
218
+
219
+ const img1 = renderJpegFile(filePath1, {
220
+ columns: singleMaxWidth + 4,
221
+ rows,
222
+ maxWidth: singleMaxWidth,
223
+ maxRows,
224
+ indent: '',
225
+ });
226
+
227
+ const img2 = renderJpegFile(filePath2, {
228
+ columns: singleMaxWidth + 4,
229
+ rows,
230
+ maxWidth: singleMaxWidth,
231
+ maxRows,
232
+ indent: '',
233
+ });
234
+
235
+ if (!sideBySide) {
236
+ return [
237
+ ...img1.map(line => ` ${line}`),
238
+ '',
239
+ ...img2.map(line => ` ${line}`),
240
+ ];
241
+ }
242
+
243
+ const maxLines = Math.max(img1.length, img2.length);
244
+ const combined = [];
245
+ const gap = ' ';
246
+
247
+ for (let i = 0; i < maxLines; i++) {
248
+ const line1 = img1[i] || '';
249
+ const line2 = img2[i] || '';
250
+ combined.push(` ${line1}${gap}${line2}`);
251
+ }
252
+
253
+ return combined;
254
+ }
255
+
256
+ export function getWelcomeGalleryLines(count = 2, {
257
+ directory = DEFAULT_IMAGE_DIRECTORY,
258
+ env = process.env,
259
+ isTTY = process.stdout.isTTY,
260
+ columns = process.stdout.columns || 80,
261
+ rows = process.stdout.rows || 24,
262
+ } = {}) {
263
+ const images = discoverImages(directory);
264
+ if (images.length === 0 || !supportsTerminalImages({ env, isTTY })) return [];
265
+
266
+ if (count >= 2 && images.length >= 2) {
267
+ try {
268
+ const pairLines = renderTwoImagesSideBySide(images[0], images[1], { columns, rows, maxRows: 14 });
269
+ return [
270
+ '',
271
+ ` \x1b[1m\x1b[38;2;255;126;179m◆ JU JINGYI • 鞠婧祎 GALLERY\x1b[0m \x1b[38;2;148;163;184m• Featured Visuals (1 & 2 / ${images.length})\x1b[0m`,
272
+ ...pairLines,
273
+ '',
274
+ ];
275
+ } catch {
276
+ return [];
277
+ }
278
+ }
279
+
280
+ try {
281
+ const singleLines = renderJpegFile(images[0], { columns, rows, maxWidth: 44, maxRows: 14 });
282
+ return [
283
+ '',
284
+ ` \x1b[1m\x1b[38;2;255;126;179m◆ JU JINGYI • 鞠婧祎 GALLERY\x1b[0m \x1b[38;2;148;163;184m• Featured Visual (1/${images.length})\x1b[0m`,
285
+ ...singleLines,
286
+ '',
287
+ ];
288
+ } catch {
289
+ return [];
290
+ }
291
+ }
292
+
210
293
  export function createTerminalImageGallery({
211
294
  directory = DEFAULT_IMAGE_DIRECTORY,
212
295
  frequency = 3,
@@ -222,10 +305,32 @@ export function createTerminalImageGallery({
222
305
  return {
223
306
  enabled,
224
307
  imageCount: images.length,
225
- maybeRender(completedQueries = 0, { force = false } = {}) {
308
+ maybeRender(completedQueries = 0, { force = false, count = 1 } = {}) {
226
309
  const cadence = Math.max(1, Math.floor(frequency));
227
310
  if (!enabled || (!force && (completedQueries <= 0 || completedQueries % cadence !== 0))) return [];
228
311
 
312
+ if (count >= 2 && images.length >= 2) {
313
+ const idx1 = imageIndex % images.length;
314
+ const idx2 = (imageIndex + 1) % images.length;
315
+ const path1 = images[idx1];
316
+ const path2 = images[idx2];
317
+ try {
318
+ const pairLines = renderTwoImagesSideBySide(path1, path2, {
319
+ columns: getColumns(),
320
+ rows: getRows(),
321
+ });
322
+ imageIndex = (imageIndex + 2) % images.length;
323
+ return [
324
+ '',
325
+ ` \x1b[1m\x1b[38;2;255;126;179m◆ KIKU GALLERY\x1b[0m \x1b[38;2;148;163;184m• Photos ${idx1 + 1} & ${idx2 + 1}/${images.length}\x1b[0m`,
326
+ ...pairLines,
327
+ '',
328
+ ];
329
+ } catch {
330
+ return [];
331
+ }
332
+ }
333
+
229
334
  const currentIndex = imageIndex % images.length;
230
335
  const filePath = images[currentIndex];
231
336
  try {
@@ -246,3 +351,4 @@ export function createTerminalImageGallery({
246
351
  },
247
352
  };
248
353
  }
354
+
@@ -130,9 +130,13 @@ function getGraphemeDisplayWidth(segment) {
130
130
  if (
131
131
  (code >= 0x1100 && code <= 0x11FF) ||
132
132
  (code >= 0x2E80 && code <= 0x9FFF) ||
133
+ (code >= 0x3000 && code <= 0x303F) ||
133
134
  (code >= 0xAC00 && code <= 0xD7A3) ||
134
135
  (code >= 0xF900 && code <= 0xFAFF) ||
135
- (code >= 0xFE30 && code <= 0xFE4F)
136
+ (code >= 0xFE30 && code <= 0xFE4F) ||
137
+ (code >= 0xFF01 && code <= 0xFF60) ||
138
+ (code >= 0xFFE0 && code <= 0xFFE6) ||
139
+ (code >= 0x20000 && code <= 0x3FFFF)
136
140
  ) {
137
141
  width += 2;
138
142
  } else {
@@ -297,7 +301,7 @@ export function wrapText(text, maxWidth = 72) {
297
301
 
298
302
 
299
303
  export function stripAnsi(str) {
300
- return str.replace(/\x1b\[[0-9;]*m/g, '');
304
+ return String(str ?? '').replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))/g, '');
301
305
  }
302
306
 
303
307
  export function sanitizeTerminalText(value, maxLength = 2000) {
@@ -751,8 +755,8 @@ export function formatTerminalMarkdown(text) {
751
755
  if (!text) return '';
752
756
  return text
753
757
  .replace(/^###\s*(.+)$/gm, `${C.bold}${C.gold}✦ $1${C.reset}`)
754
- .replace(/^##\s*(.+)$/gm, `\n${C.bold}${C.roseGlow}■ $1${C.reset}`)
755
- .replace(/^#\s*(.+)$/gm, `\n${C.bold}${C.sakura}━━━ $1 ━━━${C.reset}`)
758
+ .replace(/^##\s*(.+)$/gm, `${C.bold}${C.roseGlow}■ $1${C.reset}`)
759
+ .replace(/^#\s*(.+)$/gm, `${C.bold}${C.sakura}━━━ $1 ━━━${C.reset}`)
756
760
  .replace(/^>\s*(.+)$/gm, ` ${C.slate}│${C.reset} ${C.italic}${C.white}$1${C.reset}`)
757
761
  .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, `${C.bold}${C.cyberCyan}$1${C.reset} ➔ ${C.underline}${C.roseGlow}$2${C.reset}`)
758
762
  .replace(/\*{3}([^*]+)\*{3}/g, `${C.bold}${C.italic}${C.white}$1${C.reset}`)
@@ -854,7 +858,7 @@ export function formatMarkdownLines(text, maxWidth = getFullWidth() - 8) {
854
858
  return result;
855
859
  }
856
860
 
857
- export function getWelcomeHeroLines() {
861
+ export function getWelcomeHeroLines() {
858
862
  const p = C.sakura;
859
863
  const r = C.roseGlow;
860
864
  const g = C.gold;
@@ -866,8 +870,8 @@ export function getWelcomeHeroLines() {
866
870
  const b = C.bold;
867
871
  const d = C.dim;
868
872
  const rst = C.reset;
869
- const boxWidth = Math.min(84, Math.max(50, getFullWidth() - 4));
870
- const compact = getFullWidth() < 120;
873
+ const boxWidth = Math.min(84, Math.max(50, getFullWidth() - 4));
874
+ const compact = getFullWidth() < 120;
871
875
  const headerTitle = 'Suggested Topics & Queries';
872
876
  const topDashCount = Math.max(2, boxWidth - getStringDisplayWidth(headerTitle) - 6);
873
877
  const innerWidth = boxWidth - 2;
@@ -880,28 +884,30 @@ export function getWelcomeHeroLines() {
880
884
  return ` ${s}│${rst} ${icon} ${tag}${rst}${' '.repeat(Math.max(0, tagWidth - getStringDisplayWidth(tag)))} ${w}${text}${rst}${' '.repeat(pad)}${s}│${rst}`;
881
885
  };
882
886
 
883
- const heroLines = compact ? [
884
- ` ${b}${p}◆ Ju Jingyi • 鞠婧祎 • Mìjú Terminal Portal ◆${rst}`,
885
- ` ${p}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${rst}`,
886
- ` ${r}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${rst}`,
887
- ] : [
888
- ` ${p}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${rst} ${b}${p}◆ Ju Jingyi • 鞠婧祎 • Mìjú Terminal Portal ◆${rst}`,
889
- ` ${p}██║ ██╔╝██║██║ ██╔╝██║ ██║ ██╔══██╗██║${rst} ${d}${s}Interactive Fan Knowledge Base & Autonomous Agent${rst}`,
890
- ` ${r}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${rst} ${d}${s}Filmography • Discography • Fashion & Awards${rst}`,
891
- ` ${r}██╔═██╗ ██║██╔═██╗ ██║ ██║ ██╔══██╗██║${rst} ${c}● Live Ready${rst} ${s}• Multilingual (CN / EN / TH)${rst}`,
892
- ` ${p}██║ ╚██╗██║██║ ╚██╗╚██████╔╝ ██║ ██║██║${rst}`,
893
- ` ${p}╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝${rst}`,
894
- ];
895
- const shortcutLines = compact ? [
896
- ` ${s}Shortcuts: ${b}${w}[Tab]${rst} ${s}Commands • ${b}${w}[↑/↓]${rst} ${s}History • ${b}${w}[F2]${rst} ${s}Details${rst}`,
897
- ` ${b}${w}[PgUp/PgDn]${rst} ${s}Scroll • ${b}${w}[Ctrl+C]${rst} ${s}Exit${rst}`,
898
- ] : [
899
- ` ${s}Shortcuts: ${b}${w}[Tab]${rst} ${s}Autocomplete • ${b}${w}[↑/↓]${rst} ${s}History • ${b}${w}[PgUp/PgDn]${rst} ${s}Scroll • ${b}${w}[F2]${rst} ${s}Details • ${b}${w}[Ctrl+C]${rst} ${s}Exit${rst}`,
900
- ];
901
-
902
- return [
903
- ...(compact ? [] : ['']),
904
- ...heroLines,
887
+ const heroLines = compact ? [
888
+ ` ${p}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${rst}`,
889
+ ` ${r}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${rst}`,
890
+ ` ${p}██║ ██║██║██║ ██║╚██████╔╝ ██║ ██║██║${rst}`,
891
+ ] : [
892
+ ` ${p}██╗ ██╗██╗██╗ ██╗██╗ ██╗ █████╗ ██╗${rst}`,
893
+ ` ${p}██║ ██╔╝██║██║ ██╔╝██║ ██║ ██╔══██╗██║${rst}`,
894
+ ` ${r}█████═╝ ██║█████═╝ ██║ ██║ ███████║██║${rst}`,
895
+ ` ${r}██╔═██╗ ██║██╔═██╗ ██║ ██║ ██╔══██╗██║${rst}`,
896
+ ` ${p}██║ ╚██╗██║██║ ╚██╗╚██████╔╝ ██║ ██║██║${rst}`,
897
+ ` ${p}╚═╝ ╚═╝╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝${rst}`,
898
+ ];
899
+ const shortcutLines = compact ? [
900
+ ` ${s}Keys: ${b}${w}[Tab]${rst} ${s}Cmds • ${b}${w}[↑/↓]${rst} ${s}Hist • ${b}${w}[F1]${rst} ${s}Help • ${b}${w}[F2]${rst} ${s}Details${rst}`,
901
+ ` ${b}${w}[F3]${rst} ${s}Think • ${b}${w}[F4]${rst} ${s}Tools • ${b}${w}[F5]${rst} ${s}Copy • ${b}${w}[Ctrl+C]${rst} ${s}Exit${rst}`,
902
+ ] : [
903
+ ` ${s}Keys: ${b}${w}[Tab]${rst} ${s}Autocomplete • ${b}${w}[↑/↓]${rst} ${s}History • ${b}${w}[PgUp/PgDn]${rst} ${s}Scroll • ${b}${w}[Ctrl+C]${rst} ${s}Exit${rst}`,
904
+ ` ${s}F-Keys: ${b}${w}[F1]${rst} ${s}Help • ${b}${w}[F2]${rst} ${s}Details • ${b}${w}[F3]${rst} ${s}Thinking • ${b}${w}[F4]${rst} ${s}Tools • ${b}${w}[F5]${rst} ${s}Copy • ${b}${w}[F6]${rst} ${s}Gallery • ${b}${w}[F7]${rst} ${s}Theme${rst}`,
905
+ ` ${s}Ctrl: ${b}${w}[Ctrl+T]${rst} ${s}Toggle Think • ${b}${w}[Ctrl+R]${rst} ${s}Retry • ${b}${w}[Ctrl+S]${rst} ${s}Save • ${b}${w}[Ctrl+G]${rst} ${s}Gallery${rst}`,
906
+ ];
907
+
908
+ return [
909
+ ...(compact ? [] : ['']),
910
+ ...heroLines,
905
911
  '',
906
912
  ` ${s}╭──${rst} ${b}${g}${headerTitle}${rst} ${s}${'─'.repeat(topDashCount)}╮${rst}`,
907
913
  padItem(p + '◆', '2026 Works', '"What are Ju Jingyi\'s 2026 upcoming dramas?"'),
@@ -910,7 +916,7 @@ export function getWelcomeHeroLines() {
910
916
  padItem(l + '◆', 'Tools / MCP', '"@search_works 2026, /mcp list, /stats, /help"'),
911
917
  ` ${s}╰${'─'.repeat(boxWidth)}╯${rst}`,
912
918
  '',
913
- ...shortcutLines,
919
+ ...shortcutLines,
914
920
  '',
915
921
  ];
916
922
  }
@@ -1068,19 +1074,23 @@ export function formatTaskPlanBanner(plan) {
1068
1074
  }).join('\n');
1069
1075
  }
1070
1076
 
1077
+ let isAlternateScreenActive = false;
1078
+
1071
1079
  export function enterAlternateScreen() {
1072
- if (process.stdout.isTTY) {
1080
+ if (process.stdout.isTTY && !isAlternateScreenActive) {
1081
+ isAlternateScreenActive = true;
1073
1082
  _rawStdoutWrite('\x1b[?1049h\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1005l\x1b[?1006l\x1b[?1015l\x1b[?25l\x1b[H\x1b[2J');
1074
1083
  }
1075
1084
  }
1076
1085
 
1077
1086
  export function exitAlternateScreen() {
1078
- if (process.stdout.isTTY) {
1079
- _rawStdoutWrite('\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1005l\x1b[?1006l\x1b[?1015l\x1b[?1049l\x1b[?25h\x1b[0m\r\n');
1087
+ if (process.stdout.isTTY && isAlternateScreenActive) {
1088
+ isAlternateScreenActive = false;
1089
+ _rawStdoutWrite('\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1005l\x1b[?1006l\x1b[?1015l\x1b[?1049l\x1b[?25h\x1b[0m');
1080
1090
  }
1081
1091
  }
1082
1092
 
1083
- export function getTuiViewportLayout(termRows = 24) {
1093
+ export function getTuiViewportLayout(termRows = 24) {
1084
1094
  const parsedRows = Number(termRows);
1085
1095
  const rows = Math.max(10, Number.isFinite(parsedRows) ? Math.floor(parsedRows) : 24);
1086
1096
  const viewportStartRow = 1;
@@ -1095,16 +1105,16 @@ export function getTuiViewportLayout(termRows = 24) {
1095
1105
  statusBarRow,
1096
1106
  promptRow,
1097
1107
  separatorRow,
1098
- };
1099
- }
1100
-
1101
- export function getNewOutputScrollOffset(addedLineCount, termRows = 24, currentOffset = 0) {
1102
- const added = Math.max(0, Number.isFinite(Number(addedLineCount)) ? Math.floor(Number(addedLineCount)) : 0);
1103
- if (currentOffset > 0) return currentOffset + added;
1104
- return Math.max(0, added - getTuiViewportLayout(termRows).viewportHeight);
1105
- }
1106
-
1107
- export class VirtualScreenBuffer {
1108
+ };
1109
+ }
1110
+
1111
+ export function getNewOutputScrollOffset(addedLineCount, termRows = 24, currentOffset = 0) {
1112
+ const added = Math.max(0, Number.isFinite(Number(addedLineCount)) ? Math.floor(Number(addedLineCount)) : 0);
1113
+ if (currentOffset > 0) return currentOffset + added;
1114
+ return Math.max(0, added - getTuiViewportLayout(termRows).viewportHeight);
1115
+ }
1116
+
1117
+ export class VirtualScreenBuffer {
1108
1118
  constructor(width = 80, height = 24, writer = null) {
1109
1119
  this.width = Math.max(10, width);
1110
1120
  this.height = Math.max(5, height);