kiku-agent 1.0.11 → 1.2.0

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 (221) hide show
  1. package/README.md +300 -495
  2. package/package.json +12 -11
  3. package/public/fonts/GoogleSans-Bold.ttf +0 -0
  4. package/public/fonts/GoogleSans-Bold.woff2 +0 -0
  5. package/public/fonts/GoogleSans-BoldItalic.ttf +0 -0
  6. package/public/fonts/GoogleSans-Italic.ttf +0 -0
  7. package/public/fonts/GoogleSans-Latin-400.woff2 +0 -0
  8. package/public/fonts/GoogleSans-Latin-500.woff2 +0 -0
  9. package/public/fonts/GoogleSans-Latin-600.woff2 +0 -0
  10. package/public/fonts/GoogleSans-Latin-700.woff2 +0 -0
  11. package/public/fonts/GoogleSans-Medium.ttf +0 -0
  12. package/public/fonts/GoogleSans-Medium.woff2 +0 -0
  13. package/public/fonts/GoogleSans-Regular.ttf +0 -0
  14. package/public/fonts/GoogleSans-Regular.woff2 +0 -0
  15. package/public/fonts/GoogleSans-Thai-400.woff2 +0 -0
  16. package/public/fonts/GoogleSans-Thai-500.woff2 +0 -0
  17. package/public/fonts/GoogleSans-Thai-600.woff2 +0 -0
  18. package/public/fonts/GoogleSans-Thai-700.woff2 +0 -0
  19. package/public/fonts/NotoSansThai-Bold.woff2 +0 -0
  20. package/public/fonts/NotoSansThai-Regular.woff2 +0 -0
  21. package/public/fonts/google-sans-official.css +900 -0
  22. package/public/images/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
  23. package/public/images/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
  24. package/public/images/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
  25. package/public/images/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
  26. package/public/images/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
  27. package/public/images/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
  28. package/public/images/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
  29. package/public/images/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
  30. package/public/images/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
  31. package/public/images/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
  32. package/public/images/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
  33. package/public/images/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
  34. package/public/images/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
  35. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-399915.jpg +0 -0
  36. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-424041.jpg +0 -0
  37. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-588166.jpg +0 -0
  38. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-672198.jpg +0 -0
  39. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-700522.jpg +0 -0
  40. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026820-839722.jpg +0 -0
  41. package/public/images/douyin-update/thumbs//346/212/226/351/237/2632026825-460586.jpg +0 -0
  42. package/public/images/douyin-update/thumbs//346/212/226/351/237/263202694-689407.jpg +0 -0
  43. package/public/images/douyin-update//346/212/226/351/237/2632026820-399915.mp4 +0 -0
  44. package/public/images/douyin-update//346/212/226/351/237/2632026820-424041.mp4 +0 -0
  45. package/public/images/douyin-update//346/212/226/351/237/2632026820-588166.mp4 +0 -0
  46. package/public/images/douyin-update//346/212/226/351/237/2632026820-672198.mp4 +0 -0
  47. package/public/images/douyin-update//346/212/226/351/237/2632026820-700522.mp4 +0 -0
  48. package/public/images/douyin-update//346/212/226/351/237/2632026820-839722.mp4 +0 -0
  49. package/public/images/douyin-update//346/212/226/351/237/2632026825-460586.mp4 +0 -0
  50. package/public/images/douyin-update//346/212/226/351/237/263202694-689407.mp4 +0 -0
  51. package/public/images/general/0040jbadgy1id6am4yxqfj61s82dn1kx02.jpg +0 -0
  52. package/public/images/general/0040jbadgy1id6am708pbj62c0340qv602.jpg +0 -0
  53. package/public/images/general/0040jbadgy1id6amb81uqj62c0340x6q02.jpg +0 -0
  54. package/public/images/general/0040jbadgy1id6amcqte3j62c0340qv602.jpg +0 -0
  55. package/public/images/general/0040jbadgy1id6amdg3xcj62b232wqv502.jpg +0 -0
  56. package/public/images/general/0040jbadgy1id6amexv23j620v2pau0x02.jpg +0 -0
  57. package/public/images/general/0040jbadly8icq3if7twwj60qy0qy0uc02.jpg +0 -0
  58. package/public/images/general/008ET7Legy1ig9s89nri5j30i20n1gn4.jpg +0 -0
  59. package/public/images/general/008ET7Legy1ig9s8a2lmlj30i20ny409.jpg +0 -0
  60. package/public/images/general/008ET7Legy1ig9s8ay1hsj30i20nk3zv.jpg +0 -0
  61. package/public/images/general/008ET7Legy1ig9s8bab6sj30i20o7go1.jpg +0 -0
  62. package/public/images/general/008ET7Legy1ig9s8blqsxj30i20oiq4r.jpg +0 -0
  63. package/public/images/general/008ET7Legy1ig9s8cbxkzj30i20mxmzh.jpg +0 -0
  64. package/public/images/general/jujingyi_portrait.jpg +0 -0
  65. package/public/images/jujingyi_portrait.jpg +0 -0
  66. package/public/images/series/a-millenniums-wait/poster.jpg +0 -0
  67. package/public/images/series/beauty-of-resilience/poster.jpg +0 -0
  68. package/public/images/series/detective-samoyeds/poster.jpg +0 -0
  69. package/public/images/series/everyday/poster.jpg +0 -0
  70. package/public/images/series/fu-rong-ancient-painting/poster.jpg +0 -0
  71. package/public/images/series/in-a-class-of-her-own/poster.jpg +0 -0
  72. package/public/images/series/in-blossom/poster.jpg +0 -0
  73. package/public/images/series/legend-of-yunxi/poster.jpg +0 -0
  74. package/public/images/series/lets-fight/poster-2.jpg +0 -0
  75. package/public/images/series/lets-fight/poster.jpg +0 -0
  76. package/public/images/series/love-emergency/poster.jpg +0 -0
  77. package/public/images/series/love-under-the-full-moon/poster.jpg +0 -0
  78. package/public/images/series/money-bastion/poster.jpg +0 -0
  79. package/public/images/series/mr-swimmer/poster.jpg +0 -0
  80. package/public/images/series/novoland-castle-in-the-sky/poster.jpg +0 -0
  81. package/public/images/series/please-give-me-a-pair-of-wings/poster.jpg +0 -0
  82. package/public/images/series/rebirth-for-you/poster.jpg +0 -0
  83. package/public/images/series/sword-and-fairy-4/poster.jpg +0 -0
  84. package/public/images/series/the-blooms-at-ruyi-pavilion/poster.jpg +0 -0
  85. package/public/images/series/the-eternal-fragrance/poster-2.jpg +0 -0
  86. package/public/images/series/the-eternal-fragrance/poster.jpg +0 -0
  87. package/public/images/series/the-fallen-flowers/poster.jpg +0 -0
  88. package/public/images/series/the-legend-of-white-snake/poster.jpg +0 -0
  89. package/public/images/series/veil-of-shadows/poster.jpg +0 -0
  90. package/public/images/series/wanhua-sin/poster-2.jpg +0 -0
  91. package/public/images/series/wanhua-sin/poster.jpg +0 -0
  92. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj10yogj62u03s0npe02.jpg +0 -0
  93. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj2pdvyj61li25jkjl02.jpg +0 -0
  94. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj71177j62672w9e8302.jpg +0 -0
  95. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj7twpfj62192197rw02.jpg +0 -0
  96. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj8jfu8j62c03401kx02.jpg +0 -0
  97. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkj9dhdsj622k2l77wh02.jpg +0 -0
  98. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkja31s4j62bw3407r802.jpg +0 -0
  99. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjcf331j61402ey1kx02.jpg +0 -0
  100. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjdx8gqj62602pihdt02.jpg +0 -0
  101. package/public/images/weibo-update/41/345/210/206/351/222/237/345/211/215/0040jbadgy1igrkjfvxnzj62432n57wh02.jpg +0 -0
  102. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixniv2yj627y4fxb2b02.jpg +0 -0
  103. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixsvbdaj625k2vfu1102.jpg +0 -0
  104. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixxxdaoj62c03501l202.jpg +0 -0
  105. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3ixzeef9j62c0340npd02.jpg +0 -0
  106. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy0vs6tj626t4rbhdu02.jpg +0 -0
  107. package/public/images/weibo-update/8-14-17-16/0040jbadgy1ig3iy21wwuj62bt34hx6q02.jpg +0 -0
  108. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly4wr85j62z629y7wh02.jpg +0 -0
  109. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly67oilj623u2t5npd02.jpg +0 -0
  110. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwly9qtw8j62c0340u0x02.jpg +0 -0
  111. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyem4m4j63402c01kz02.jpg +0 -0
  112. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlygkoq4j627s2yeavd02.jpg +0 -0
  113. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlyjh0daj62bw340nm102.jpg +0 -0
  114. package/public/images/weibo-update/8-8-17-39/0040jbadgy1ifwlylejlhj62bw3401kx02.jpg +0 -0
  115. package/public/images/weibo-update/today/0040jbadgy1igaiaa8b7fj627j2y67wh02.jpg +0 -0
  116. package/public/images/weibo-update/today/0040jbadgy1igaiagw3ezj61s02ebu0x02.jpg +0 -0
  117. package/public/images/weibo-update/today/0040jbadgy1igaiaivng3j61rv2ebx6p02.jpg +0 -0
  118. package/public/images/weibo-update/today/0040jbadgy1igaiajuixvj62bw340qv502.jpg +0 -0
  119. package/public/images/weibo-update/today/0040jbadgy1igaiapyartj62bw340hdt02.jpg +0 -0
  120. package/public/images/weibo-update/today/0040jbadgy1igaibkwu4ij61vy2iq1kx02.jpg +0 -0
  121. package/public/images/weibo-update/today/0040jbadgy1igaic1fvkpj61u92eo1ky02.jpg +0 -0
  122. package/public/images/weibo-update/today/0040jbadgy1igaic3cruhj62822ywb2902.jpg +0 -0
  123. package/public/manifest.json +25 -0
  124. package/public/mcp-logo-128.png +0 -0
  125. package/public/mcp-logo-64.png +0 -0
  126. package/public/mcp-logo.svg +16 -0
  127. package/server/agent/agentCore.js +542 -74
  128. package/server/agent/appPaths.js +1 -0
  129. package/server/agent/audioPlayer.js +4 -2
  130. package/server/agent/citations.js +294 -0
  131. package/server/agent/clarificationAnalytics.js +477 -0
  132. package/server/agent/clarifyingQuestions.js +1089 -0
  133. package/server/agent/cli.js +549 -125
  134. package/server/agent/commandFeatures.js +1 -0
  135. package/server/agent/commandRegistry.js +946 -879
  136. package/server/agent/commands.js +546 -272
  137. package/server/agent/config.js +581 -412
  138. package/server/agent/contextCompactor.js +2 -2
  139. package/server/agent/debate.js +235 -0
  140. package/server/agent/eventBus.js +3 -0
  141. package/server/agent/feedbackLearning.js +217 -0
  142. package/server/agent/llmClient.js +512 -394
  143. package/server/agent/mcpClient.js +182 -32
  144. package/server/agent/memory.js +283 -224
  145. package/server/agent/modelManager.js +79 -39
  146. package/server/agent/profiles.js +178 -0
  147. package/server/agent/publicTools.js +22 -0
  148. package/server/agent/queryDetails.js +68 -8
  149. package/server/agent/quizData.js +398 -384
  150. package/server/agent/research.js +121 -79
  151. package/server/agent/sanitizer.js +104 -13
  152. package/server/agent/session.js +319 -11
  153. package/server/agent/subagent.js +8 -3
  154. package/server/agent/suggestionTui.js +294 -0
  155. package/server/agent/suggestions.js +69 -3
  156. package/server/agent/telemetry.js +119 -0
  157. package/server/agent/terminalImage.js +146 -27
  158. package/server/agent/theme.js +17 -0
  159. package/server/agent/toolRouter.js +16 -1
  160. package/server/agent/tools.js +424 -225
  161. package/server/agent/ui.js +486 -64
  162. package/server/agent/version.js +2 -4
  163. package/server/agent/webSearch.js +111 -11
  164. package/server/agent/youtubeAuth.js +24 -6
  165. package/server/apiRouter.js +4222 -0
  166. package/server/mcp/data/discography.js +25 -16
  167. package/server/mcp/data/gallery.js +308 -20
  168. package/server/mcp/data/profile.js +74 -44
  169. package/server/mcp/data/timeline.js +1 -1
  170. package/server/mcp/data/works.js +51 -17
  171. package/server/mcp/dataAdapter.js +5 -3
  172. package/server/mcp/handler.js +5 -11
  173. package/server/mcp/prompts.js +86 -0
  174. package/server/mcp/security.js +18 -15
  175. package/server/mcp/server.js +7 -2
  176. package/server/mcp/tools.js +66 -2
  177. package/server/web-chat/core/browserLauncher.js +18 -0
  178. package/server/web-chat/core/connection.js +194 -0
  179. package/server/web-chat/core/conversationContext.js +82 -0
  180. package/server/web-chat/core/directCatalogEngine.js +196 -0
  181. package/server/web-chat/core/dramaKnowledge.js +142 -0
  182. package/server/web-chat/core/errorHandler.js +51 -0
  183. package/server/web-chat/core/followUpEngine.js +182 -0
  184. package/server/web-chat/core/futureEventGuard.js +80 -0
  185. package/server/web-chat/core/memorySnapshot.js +12 -0
  186. package/server/web-chat/core/security.js +146 -0
  187. package/server/web-chat/core/toolInspector.js +52 -0
  188. package/server/web-chat/router.js +120 -0
  189. package/server/web-chat/routes/chatRoute.js +397 -0
  190. package/server/web-chat/routes/clarifyConfigRoute.js +98 -0
  191. package/server/web-chat/routes/clarifyRoute.js +83 -0
  192. package/server/web-chat/routes/compareRoute.js +79 -0
  193. package/server/web-chat/routes/exportRoute.js +59 -0
  194. package/server/web-chat/routes/feedbackRoute.js +57 -0
  195. package/server/web-chat/routes/newsRoute.js +33 -0
  196. package/server/web-chat/routes/quizRoute.js +11 -0
  197. package/server/web-chat/routes/staticRoute.js +103 -0
  198. package/server/web-chat/routes/statusRoute.js +52 -0
  199. package/server/web-chat/routes/suggestRoute.js +77 -0
  200. package/server/web-chat/routes/timelineRoute.js +34 -0
  201. package/server/web-chat/routes/verifyRoute.js +57 -0
  202. package/server/web-chat/server.js +116 -0
  203. package/server/web-chat/ui/htmlBuilder.js +91 -0
  204. package/server/web-chat/ui/previews/agentBubble.preview.html +134 -0
  205. package/server/web-chat/ui/scripts/agentBubbleSystem.js +320 -0
  206. package/server/web-chat/ui/scripts/client.js +8397 -0
  207. package/server/web-chat/ui/scripts/kikuDb.js +177 -0
  208. package/server/web-chat/ui/scripts/suggestionsData.js +118 -0
  209. package/server/web-chat/ui/scripts/webchatI18n.js +291 -0
  210. package/server/web-chat/ui/styles/main.css +8568 -0
  211. package/server/web-chat/ui/templates/body.html +796 -0
  212. package/server/webChatHtml.js +1 -0
  213. package/server/webChatServer.js +6 -0
  214. package/src/data/discographyData.js +892 -866
  215. package/src/data/galleryData.js +1805 -561
  216. package/src/data/profileData.js +79 -2
  217. package/src/data/socialUpdatesData.js +106 -47
  218. package/src/data/timelineData.js +1 -1
  219. package/src/data/worksData.js +25 -4
  220. package/tokens.css +97 -0
  221. package/server/agent/updateManager.js +0 -456
@@ -2,11 +2,15 @@ import { readFileSync, readdirSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import jpeg from 'jpeg-js';
5
+ import { galleryData } from '../../src/data/galleryData.js';
5
6
 
6
7
  const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
7
- export const DEFAULT_IMAGE_DIRECTORY = path.resolve(moduleDirectory, '../../src/assets/images');
8
+ export const DEFAULT_IMAGE_DIRECTORY = path.resolve(moduleDirectory, '../../public/images');
9
+ export const FALLBACK_IMAGE_DIRECTORY = path.resolve(moduleDirectory, '../../src/assets/images');
8
10
 
9
11
  const RESET = '\x1b[0m';
12
+ const BOLD = '\x1b[1m';
13
+ const DIM = '\x1b[2m';
10
14
  const foreground = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
11
15
  const background = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
12
16
  const QUADRANT_GLYPHS = [
@@ -54,7 +58,7 @@ function sampleBilinear(image, x, y, targetWidth, targetHeight) {
54
58
  return result;
55
59
  }
56
60
 
57
- function resizeAndEnhance(image, targetWidth, targetHeight) {
61
+ function resizeAndEnhance(image, targetWidth, targetHeight, { sharpness = 1.25, contrast = 1.05 } = {}) {
58
62
  const pixels = new Float32Array(targetWidth * targetHeight * 3);
59
63
  for (let y = 0; y < targetHeight; y++) {
60
64
  for (let x = 0; x < targetWidth; x++) {
@@ -78,16 +82,22 @@ function resizeAndEnhance(image, targetWidth, targetHeight) {
78
82
  const offset = (y * targetWidth + x) * 3;
79
83
  const sharpened = [0, 1, 2].map(channel => {
80
84
  const center = at(x, y, channel);
81
- const neighbourAverage = (
85
+ const crossAverage = (
82
86
  at(x - 1, y, channel) + at(x + 1, y, channel)
83
87
  + at(x, y - 1, channel) + at(x, y + 1, channel)
84
88
  ) / 4;
85
- return center * 1.18 - neighbourAverage * 0.18;
89
+ const diagAverage = (
90
+ at(x - 1, y - 1, channel) + at(x + 1, y - 1, channel)
91
+ + at(x - 1, y + 1, channel) + at(x + 1, y + 1, channel)
92
+ ) / 4;
93
+ const neighborAverage = crossAverage * 0.7 + diagAverage * 0.3;
94
+ return center * (1 + 0.28 * sharpness) - neighborAverage * (0.28 * sharpness);
86
95
  });
96
+
87
97
  const luminance = sharpened[0] * 0.2126 + sharpened[1] * 0.7152 + sharpened[2] * 0.0722;
88
98
  for (let channel = 0; channel < 3; channel++) {
89
- const saturated = luminance + (sharpened[channel] - luminance) * 1.06;
90
- enhanced[offset + channel] = clampChannel((saturated - 128) * 1.03 + 128);
99
+ const saturated = luminance + (sharpened[channel] - luminance) * 1.08;
100
+ enhanced[offset + channel] = clampChannel((saturated - 128) * contrast + 128);
91
101
  }
92
102
  }
93
103
  }
@@ -98,7 +108,8 @@ function colorDistance(left, right) {
98
108
  const red = left[0] - right[0];
99
109
  const green = left[1] - right[1];
100
110
  const blue = left[2] - right[2];
101
- return red * red + green * green + blue * blue;
111
+ // Human eye perceptual luminance weighting
112
+ return red * red * 0.299 + green * green * 0.587 + blue * blue * 0.114;
102
113
  }
103
114
 
104
115
  function averageColors(colors, fallback) {
@@ -127,7 +138,7 @@ function encodeQuadrants(colors) {
127
138
  }
128
139
 
129
140
  let assignments = [0, 0, 0, 0];
130
- for (let iteration = 0; iteration < 3; iteration++) {
141
+ for (let iteration = 0; iteration < 4; iteration++) {
131
142
  assignments = colors.map(color => colorDistance(color, second) < colorDistance(color, first) ? 1 : 0);
132
143
  first = averageColors(colors.filter((_, index) => assignments[index] === 0), first);
133
144
  second = averageColors(colors.filter((_, index) => assignments[index] === 1), second);
@@ -147,6 +158,8 @@ export function renderJpegToAnsi(buffer, {
147
158
  maxWidth = 60,
148
159
  maxRows = 20,
149
160
  indent = ' ',
161
+ sharpness = 1.25,
162
+ contrast = 1.05,
150
163
  } = {}) {
151
164
  const image = jpeg.decode(buffer, { useTArray: true, formatAsRGBA: true });
152
165
  if (!image?.width || !image?.height) throw new Error('Unable to decode gallery image');
@@ -165,7 +178,7 @@ export function renderJpegToAnsi(buffer, {
165
178
  }
166
179
 
167
180
  const sampledWidth = targetWidth * 2;
168
- const resized = resizeAndEnhance(image, sampledWidth, targetPixelHeight);
181
+ const resized = resizeAndEnhance(image, sampledWidth, targetPixelHeight, { sharpness, contrast });
169
182
  const pixelAt = (x, y) => {
170
183
  const offset = (y * sampledWidth + x) * 3;
171
184
  return [resized[offset], resized[offset + 1], resized[offset + 2]];
@@ -196,15 +209,37 @@ export function renderJpegFile(filePath, options = {}) {
196
209
  return renderJpegToAnsi(readFileSync(filePath), options);
197
210
  }
198
211
 
199
- export function discoverImages(directory) {
200
- try {
201
- return readdirSync(directory, { withFileTypes: true })
202
- .filter(entry => entry.isFile() && /\.jpe?g$/i.test(entry.name))
203
- .map(entry => path.join(directory, entry.name))
204
- .sort((a, b) => a.localeCompare(b));
205
- } catch {
206
- return [];
212
+ export function discoverImages(directory = DEFAULT_IMAGE_DIRECTORY) {
213
+ function scanDir(dir) {
214
+ let list = [];
215
+ try {
216
+ const entries = readdirSync(dir, { withFileTypes: true });
217
+ for (const entry of entries) {
218
+ const fullPath = path.join(dir, entry.name);
219
+ if (entry.isDirectory()) {
220
+ list = list.concat(scanDir(fullPath));
221
+ } else if (entry.isFile() && /\.jpe?g$/i.test(entry.name)) {
222
+ list.push(fullPath);
223
+ }
224
+ }
225
+ } catch {}
226
+ return list;
227
+ }
228
+
229
+ let results = scanDir(directory);
230
+ if (results.length === 0 && directory !== FALLBACK_IMAGE_DIRECTORY) {
231
+ results = scanDir(FALLBACK_IMAGE_DIRECTORY);
207
232
  }
233
+ return results.sort((a, b) => a.localeCompare(b));
234
+ }
235
+
236
+ export function findGalleryMetadata(filePath) {
237
+ if (!filePath || !Array.isArray(galleryData)) return null;
238
+ const basename = path.basename(filePath).toLowerCase();
239
+ return galleryData.find(item => {
240
+ if (!item.image) return false;
241
+ return item.image.toLowerCase().includes(basename);
242
+ }) || null;
208
243
  }
209
244
 
210
245
  export function renderTwoImagesSideBySide(filePath1, filePath2, {
@@ -214,7 +249,7 @@ export function renderTwoImagesSideBySide(filePath1, filePath2, {
214
249
  } = {}) {
215
250
  const availableWidth = Math.max(20, Math.floor(columns));
216
251
  const sideBySide = availableWidth >= 76;
217
- const singleMaxWidth = sideBySide ? Math.min(34, Math.floor((availableWidth - 8) / 2)) : Math.min(50, availableWidth - 4);
252
+ const singleMaxWidth = sideBySide ? Math.min(36, Math.floor((availableWidth - 8) / 2)) : Math.min(50, availableWidth - 4);
218
253
 
219
254
  const img1 = renderJpegFile(filePath1, {
220
255
  columns: singleMaxWidth + 4,
@@ -253,6 +288,67 @@ export function renderTwoImagesSideBySide(filePath1, filePath2, {
253
288
  return combined;
254
289
  }
255
290
 
291
+ export function renderHdGalleryCard(filePath, {
292
+ index = 0,
293
+ total = 1,
294
+ columns = process.stdout.columns || 80,
295
+ rows = process.stdout.rows || 28,
296
+ maxWidth = 64,
297
+ maxRows = 22,
298
+ } = {}) {
299
+ const meta = findGalleryMetadata(filePath);
300
+ const titleCn = meta?.titleCn || path.basename(filePath);
301
+ const titleTh = meta?.titleTh || '';
302
+ const dateStr = meta?.date || meta?.era || '';
303
+ const styleVibe = meta?.styleVibe || '';
304
+ const quote = meta?.quote?.cn || '';
305
+ const quoteTh = meta?.quote?.th || '';
306
+
307
+ const imageLines = renderJpegFile(filePath, {
308
+ columns,
309
+ rows,
310
+ maxWidth,
311
+ maxRows,
312
+ indent: ' ',
313
+ sharpness: 1.4,
314
+ contrast: 1.08,
315
+ });
316
+
317
+ const lines = [];
318
+ lines.push('');
319
+ lines.push(` ${BOLD}\x1b[38;2;255;126;179m🌸 JU JINGYI • 鞠婧祎 HD GALLERY\x1b[0m \x1b[38;2;148;163;184m• Gallery ${index + 1}/${total}\x1b[0m`);
320
+ lines.push(` \x1b[38;2;251;191;36m◆ ${titleCn}\x1b[0m ${dateStr ? `\x1b[38;2;148;163;184m(${dateStr})\x1b[0m` : ''}`);
321
+ if (titleTh) {
322
+ lines.push(` \x1b[38;2;226;232;240m${DIM}${titleTh}${RESET}`);
323
+ }
324
+ lines.push('');
325
+ lines.push(...imageLines);
326
+ lines.push('');
327
+
328
+ if (styleVibe) {
329
+ lines.push(` \x1b[38;2;244;114;182m✨ Vibe:\x1b[0m \x1b[38;2;203;213;225m${styleVibe}\x1b[0m`);
330
+ }
331
+ if (quote) {
332
+ lines.push(` \x1b[38;2;251;191;36m💬 语录:\x1b[0m \x1b[38;2;248;250;252m“${quote}”\x1b[0m`);
333
+ if (quoteTh) {
334
+ lines.push(` ${DIM} “${quoteTh}”${RESET}`);
335
+ }
336
+ }
337
+ if (meta?.colorPalette && Array.isArray(meta.colorPalette) && meta.colorPalette.length > 0) {
338
+ const swatches = meta.colorPalette.map(c => {
339
+ const hex = (c.hex || '').replace('#', '');
340
+ const r = parseInt(hex.slice(0, 2), 16) || 200;
341
+ const g = parseInt(hex.slice(2, 4), 16) || 200;
342
+ const b = parseInt(hex.slice(4, 6), 16) || 200;
343
+ return `${background(r, g, b)}${foreground(0, 0, 0)} ${c.name} ${RESET}`;
344
+ }).join(' ');
345
+ lines.push(` \x1b[38;2;148;163;184m🎨 Palette:\x1b[0m ${swatches}`);
346
+ }
347
+ lines.push('');
348
+
349
+ return lines;
350
+ }
351
+
256
352
  export function getWelcomeGalleryLines(count = 2, {
257
353
  directory = DEFAULT_IMAGE_DIRECTORY,
258
354
  env = process.env,
@@ -305,24 +401,46 @@ export function createTerminalImageGallery({
305
401
  return {
306
402
  enabled,
307
403
  imageCount: images.length,
308
- maybeRender(completedQueries = 0, { force = false, count = 1 } = {}) {
404
+ getImagePaths: () => [...images],
405
+ maybeRender(completedQueries = 0, { force = false, count = 1, hd = false, index = null } = {}) {
309
406
  const cadence = Math.max(1, Math.floor(frequency));
310
407
  if (!enabled || (!force && (completedQueries <= 0 || completedQueries % cadence !== 0))) return [];
311
408
 
409
+ const targetIdx = index !== null && !isNaN(index) ? Math.abs(Number(index)) % images.length : imageIndex % images.length;
410
+
411
+ if (hd) {
412
+ const filePath = images[targetIdx];
413
+ try {
414
+ const hdCard = renderHdGalleryCard(filePath, {
415
+ index: targetIdx,
416
+ total: images.length,
417
+ columns: getColumns(),
418
+ rows: getRows(),
419
+ maxWidth: 62,
420
+ maxRows: 22,
421
+ });
422
+ imageIndex = (targetIdx + 1) % images.length;
423
+ return hdCard;
424
+ } catch {
425
+ return [];
426
+ }
427
+ }
428
+
312
429
  if (count >= 2 && images.length >= 2) {
313
- const idx1 = imageIndex % images.length;
314
- const idx2 = (imageIndex + 1) % images.length;
430
+ const idx1 = targetIdx;
431
+ const idx2 = (targetIdx + 1) % images.length;
315
432
  const path1 = images[idx1];
316
433
  const path2 = images[idx2];
317
434
  try {
318
435
  const pairLines = renderTwoImagesSideBySide(path1, path2, {
319
436
  columns: getColumns(),
320
437
  rows: getRows(),
438
+ maxRows: 16,
321
439
  });
322
- imageIndex = (imageIndex + 2) % images.length;
440
+ imageIndex = (targetIdx + 2) % images.length;
323
441
  return [
324
442
  '',
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`,
443
+ ` \x1b[1m\x1b[38;2;255;126;179m🌸 JU JINGYI • 鞠婧祎 GALLERY\x1b[0m \x1b[38;2;148;163;184m• Photos ${idx1 + 1} & ${idx2 + 1}/${images.length}\x1b[0m`,
326
444
  ...pairLines,
327
445
  '',
328
446
  ];
@@ -331,17 +449,18 @@ export function createTerminalImageGallery({
331
449
  }
332
450
  }
333
451
 
334
- const currentIndex = imageIndex % images.length;
335
- const filePath = images[currentIndex];
452
+ const filePath = images[targetIdx];
336
453
  try {
337
454
  const imageLines = renderJpegFile(filePath, {
338
455
  columns: getColumns(),
339
456
  rows: getRows(),
457
+ maxWidth: 60,
458
+ maxRows: 20,
340
459
  });
341
- imageIndex++;
460
+ imageIndex = (targetIdx + 1) % images.length;
342
461
  return [
343
462
  '',
344
- ` \x1b[1m\x1b[38;2;255;126;179m◆ KIKU MOMENT\x1b[0m \x1b[38;2;148;163;184m• Gallery ${currentIndex + 1}/${images.length}\x1b[0m`,
463
+ ` \x1b[1m\x1b[38;2;255;126;179m🌸 KIKU MOMENT\x1b[0m \x1b[38;2;148;163;184m• Gallery ${targetIdx + 1}/${images.length}\x1b[0m`,
345
464
  ...imageLines,
346
465
  '',
347
466
  ];
@@ -107,11 +107,28 @@ export function setTheme(name, { persist = true } = {}) {
107
107
  return getTheme(normalized);
108
108
  }
109
109
 
110
+ let explicitColorDisabled = null;
111
+
112
+ export function isColorDisabled() {
113
+ if (explicitColorDisabled !== null) return explicitColorDisabled;
114
+ if (typeof process !== 'undefined' && process.env) {
115
+ if (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== '') return true;
116
+ if (process.env.NODE_DISABLE_COLORS === '1') return true;
117
+ }
118
+ return false;
119
+ }
120
+
121
+ export function setColorDisabled(disabled) {
122
+ explicitColorDisabled = disabled === null ? null : Boolean(disabled);
123
+ }
124
+
110
125
  export function ansiForeground(rgb) {
126
+ if (isColorDisabled()) return '';
111
127
  return `\x1b[38;2;${rgb}m`;
112
128
  }
113
129
 
114
130
  export function ansiBackground(rgb) {
131
+ if (isColorDisabled()) return '';
115
132
  return `\x1b[48;2;${rgb}m`;
116
133
  }
117
134
 
@@ -58,6 +58,7 @@ const DEFAULT_UNIVERSAL_TOOLS = Object.freeze([
58
58
  'get_profile_and_fandom',
59
59
  'get_media_links',
60
60
  'web_search',
61
+ 'web_extract',
61
62
  'run_subagent',
62
63
  ]);
63
64
 
@@ -167,6 +168,7 @@ export class DynamicToolRouter {
167
168
  }
168
169
  selectedNames.add('global_search');
169
170
  selectedNames.add('web_search');
171
+ selectedNames.add('web_extract');
170
172
  }
171
173
 
172
174
  if (options.includeExtra && Array.isArray(options.includeExtra)) {
@@ -178,14 +180,27 @@ export class DynamicToolRouter {
178
180
  const routed = [...pinnedTools];
179
181
  const seenNames = new Set(pinnedTools.map(t => t.function?.name));
180
182
 
183
+
181
184
  for (const tool of this.allTools) {
182
185
  const name = tool.function?.name;
183
- if (name && !seenNames.has(name) && (selectedNames.has(name) || (name.startsWith('mcp_') && options.includeMcp !== false))) {
186
+ if (name && !name.startsWith('mcp_') && !seenNames.has(name) && selectedNames.has(name)) {
184
187
  seenNames.add(name);
185
188
  routed.push(tool);
186
189
  }
187
190
  }
188
191
 
192
+
193
+ const wantsMcpExplicitly = options.includeMcp === true || atMentions.some(m => m.toLowerCase().includes('mcp'));
194
+ if (wantsMcpExplicitly || routed.length < 3) {
195
+ for (const tool of this.allTools) {
196
+ const name = tool.function?.name;
197
+ if (name && name.startsWith('mcp_') && !seenNames.has(name) && (selectedNames.has(name) || wantsMcpExplicitly)) {
198
+ seenNames.add(name);
199
+ routed.push(tool);
200
+ }
201
+ }
202
+ }
203
+
189
204
  if (routed.length === 0) {
190
205
  return this.allTools.slice(0, 8);
191
206
  }