botrun-horse 2.30.0 → 2.31.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.
@@ -1,7 +1,7 @@
1
1
  // bin/commands/skill.mjs — skill 指令群組(list / show / search)
2
2
  // 無參數 → 列出全部技能
3
3
  // 不認識的子指令 → 當關鍵字做全文模糊搜尋
4
- import { timer, jsonOut, emitError, logProgress } from '../../lib/core/cli-utils.mjs';
4
+ import { timer, jsonOut, emitError, logProgress, VERSION } from '../../lib/core/cli-utils.mjs';
5
5
 
6
6
  /**
7
7
  * skill 指令主路由
@@ -38,7 +38,40 @@ export async function cmdSkill(subcommand, positionals, flags) {
38
38
  }
39
39
  }
40
40
 
41
- /** list — 列出所有技能(依系列分組) */
41
+ /**
42
+ * 簡易 semver 比較:a > b 回傳 1,a < b 回傳 -1,相等回傳 0
43
+ */
44
+ function semverCompare(a, b) {
45
+ const pa = a.split('.').map(Number);
46
+ const pb = b.split('.').map(Number);
47
+ for (let i = 0; i < 3; i++) {
48
+ if ((pa[i] || 0) > (pb[i] || 0)) return 1;
49
+ if ((pa[i] || 0) < (pb[i] || 0)) return -1;
50
+ }
51
+ return 0;
52
+ }
53
+
54
+ /**
55
+ * 非阻塞檢查 npm registry 最新版本,本地較舊時在 stderr 提醒
56
+ * @param {string} currentVersion - 目前版本
57
+ */
58
+ function checkLatestVersion(currentVersion) {
59
+ const url = 'https://registry.npmjs.org/botrun-horse/latest';
60
+ fetch(url, { signal: AbortSignal.timeout(3000) })
61
+ .then(r => r.ok ? r.json() : null)
62
+ .then(data => {
63
+ if (!data?.version) return;
64
+ if (semverCompare(data.version, currentVersion) > 0) {
65
+ process.stderr.write(
66
+ `\n⬆ 有新版本 v${data.version}(目前 v${currentVersion})\n` +
67
+ ` 更新指令: npx -y botrun-horse@latest skill\n\n`,
68
+ );
69
+ }
70
+ })
71
+ .catch(() => {}); // 網路失敗靜默忽略
72
+ }
73
+
74
+ /** list — 列出所有技能(依建立時間排序) */
42
75
  async function cmdSkillList(flags) {
43
76
  const elapsed = timer();
44
77
  const format = flags.format || 'text';
@@ -46,6 +79,9 @@ async function cmdSkillList(flags) {
46
79
 
47
80
  const all = listPrompts();
48
81
 
82
+ // 非阻塞版本檢查
83
+ checkLatestVersion(VERSION);
84
+
49
85
  if (format === 'json') {
50
86
  const data = all.map(p => ({
51
87
  slug: p.slug,
@@ -57,6 +93,7 @@ async function cmdSkillList(flags) {
57
93
  return;
58
94
  }
59
95
 
96
+ process.stdout.write(`botrun-horse v${VERSION} — ${all.length} 個技能\n\n`);
60
97
  for (let i = 0; i < all.length; i++) {
61
98
  if (i > 0) process.stdout.write('\n');
62
99
  process.stdout.write(all[i].rawContent + '\n');
@@ -29,23 +29,38 @@ export function parseFrontmatter(content) {
29
29
  }
30
30
 
31
31
  /**
32
- * 遞迴掃描目錄下所有 .md 檔案
32
+ * 遞迴收集目錄下所有 .md 檔案(含建立時間)
33
33
  * @param {string} dir - 起始目錄
34
34
  * @param {string} baseDir - 根目錄(用於計算相對路徑)
35
- * @returns {string[]} 相對路徑陣列
35
+ * @returns {{ relPath: string, birthMs: number }[]}
36
36
  */
37
- function scanMarkdownFiles(dir, baseDir) {
37
+ function collectMarkdownFiles(dir, baseDir) {
38
38
  const results = [];
39
39
  if (!fs.existsSync(dir)) return results;
40
40
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
41
41
  const fullPath = path.join(dir, entry.name);
42
42
  if (entry.isDirectory()) {
43
- results.push(...scanMarkdownFiles(fullPath, baseDir));
43
+ results.push(...collectMarkdownFiles(fullPath, baseDir));
44
44
  } else if (entry.name.endsWith('.md') && entry.name !== 'README.md') {
45
- results.push(path.relative(baseDir, fullPath));
45
+ results.push({
46
+ relPath: path.relative(baseDir, fullPath),
47
+ birthMs: fs.statSync(fullPath).birthtimeMs,
48
+ });
46
49
  }
47
50
  }
48
- return results.sort();
51
+ return results;
52
+ }
53
+
54
+ /**
55
+ * 掃描目錄下所有 .md 檔案,依建立時間排序
56
+ * @param {string} dir - 起始目錄
57
+ * @param {string} baseDir - 根目錄(用於計算相對路徑)
58
+ * @returns {string[]} 相對路徑陣列(依建立時間排序,同時間依檔名字母序)
59
+ */
60
+ function scanMarkdownFiles(dir, baseDir) {
61
+ return collectMarkdownFiles(dir, baseDir)
62
+ .sort((a, b) => a.birthMs - b.birthMs || a.relPath.localeCompare(b.relPath))
63
+ .map(r => r.relPath);
49
64
  }
50
65
 
51
66
  /**
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: botrun-horse-gemini-search-cli
3
+ description: 零框架 Gemini Search 動態搜尋 CLI — Gemini-3-Flash-Preview 即時上網搜尋、Grounding Search、URL Context、本地快取加速、串流 TTFT 最佳化
4
+ ---
5
+
6
+ 我們現在要去做出一個檢索是輸入自然語言的 MJS 的 CLI,你幫我放在適當的位置。自然語言提問以後,我要你呼叫的是 Gemini-3-Flash-Preview 這個大模型。這大模型它可以做上網搜尋,所以它可以做 Grounding Search。它可以做 URL Context Fetch,兩個工具都可以用,你就可以上網去抓資料了。但是它有一個 Thinking Budget,反正它的 Thinking Budget 肯定把它設定到最低最低,最少最少。
7
+
8
+ 然後接下來,在最快的時間之內,因為它是即時問答,所以你要越短的描述越好,不能想太久。一問、一搜、一答,就趕快答出來了。然後已經回答的答案你就把它存在快取裡面。下一次你再提問的時候你可以從快取,快取的做法是這樣:你在 ./CACHE 裡面,你把每一次回答的都有一個標題,然後有一個 .txt。所以當你在提問的時候你先把你有過的檔案的 TXT 檔名列表先丟給 MJS。如果大模型發現快取裡面有的,就直接調快取就不用上網搜尋也不用問大模型了,直接出來了。如果沒有的,在快取裡面的檔案列表都沒有的,那你就透過真實呼叫 Gemini-3-Flash-Preview 上網搜尋把它給回答掉。
9
+
10
+ 你回答的時候 Max Token 的數量,因為這應用場景是人類問答,回答的數量也不用太多,最多 2K 就好了。你要先上網搜尋最新的 SDK API,我會給你一個 Gemini API Key,你就用 Gemini 的 Direct API,然後利用它在 .env 裡面去完成這個 MJS 的 command line 的撰寫,然後把它透過 BDD、TDD、SOLID、DRY、KISS 還有 DDD 的方式去設計它,然後驗證測試到過關為止,謝謝你。
@@ -0,0 +1,10 @@
1
+ ---
2
+ name: botrun-horse-smart-cache
3
+ description: 零框架智慧快取 — System Prompt 快取清單注入、大模型語意比對取代完全命中、CSV 素材預快取轉換、民眾有感題目優先
4
+ ---
5
+
6
+ ./data 有一堆 CSV。然後我現在在 ./bb 底下有一個 gemini_search.mjs。它的快取是放在 ./ch 底下。現在快取的麻煩的點是這樣:它必須完全命中它才會拿快取,我不要完全命中才拿快取。我要你在 Gemini 搜尋的第一次的 prompt 裡面,就把 ./ch 的快取的所有檔案清單放到系統提示工程裡面,讓它知道我有哪些檔案在哪些路徑。然後如果它的輸出是說「我已經有快取」的時候,你就直接讀檔,讀那個檔案,然後直接回答就好了,就不要上網搜尋了。所以你這樣子就可以經過大模型比較聰明的,你就不用完全一樣的字你才有快取,而是在系統提示工程餵過去的時候...你要上網記得上網查一下 Gemini-3-Flash-Preview 的系統提示工程的格式,你不要亂弄。
7
+
8
+ 接下來你的快取,我想預先快取一堆的東西,就是 ./data 底下的 CSV 都是快取的素材來源。現在就麻煩大了,這些素材來源的欄位不統一,並不是檔名是相關性提問、內容是答案的格式。你幫我用一個 somehow 什麼方法,又快又準的把它轉換成我們的快取格式。我不知道你有什麼方法,反正你就把它搞定就對了。感謝。
9
+
10
+ 備註:請挑民眾有感的題目再做快取,新聞就放過它。
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: botrun-horse-trigram-search
3
+ description: 零框架 Trigram 全文檢索 CLI — MJS 自然語言拆解關鍵字、FTS5 Trigram 檢索 SQLite、Count 排序 XML 輸出、停用字過濾、給 Agentic AI 大模型調閱
4
+ ---
5
+
6
+ 我們現在要做的事情就是真的要去指導它怎麼 SELECT 出來。裡面分成幾個步驟,第一個步驟它必須去拆解你的自然語言,把它拆解成關鍵字。所以我會希望你去寫一個 MJS 的腳本,我們是 Trigram 的 T-R-I-G-R-A-M 的全文檢索或英文的全文檢索。反正你第一個腳本 MJS 你就幫我把它拆,拆成就無腦的拆,你不要用很聰明的方法,你就從第一個字、第二個字、第三個字就用 Trigram 這樣往下拆,英文也是一樣。我們就做成 SELECT 命令的拆解的程式碼了。這超級簡單的,你千萬不要把它想複雜。
7
+
8
+ 然後第二個是你就用 SELECT 命令去對它指定的 SQLite,你把整個 MJS 做成一個 CLI 可以給參數的。針對 SELECT 命令去 SELECT,SELECT 出來以後東西你就按照 Count 次數,就是把 Count 次數最小的放在最前面,零次就不用講了。關鍵字最少的那個資料你就把它放到最上面去,把它做一個 XML 的區隔的 Dump Print 出來。也就是說例如 Count 出現過一次的關鍵字擺第一個,它的資料到底是問是什麼、答是什麼,Data 是怎樣。然後 Count 5 的你就陸續擺,擺五個,它的問答是怎樣。
9
+
10
+ 超過某個 Count 次數的可能就太多了,就是那種關鍵字你就不要擺了,要不然你整個資料庫都進去了。你要注意停用字的問題,停用字要刪掉,「的」啊、「是」啊,這種停用字要刪掉。Count 超過某個 Threshold,這 Threshold 也是 CLI 參數可以指定就不要擺了。你 Dump 出來的就會是一個以 XML Tag 分開一個問答、一個問答的 Print 的資料格式。大模型拿到這個就超開心的了,因為大模型只要把自然語言丟給這個 CLI,它立刻就得到它應該從裡面找答案的內容了。
11
+
12
+ 你幫我做出來把它放在本案的適當目錄底下,要給 Agentic AI 大模型運用。
@@ -0,0 +1,6 @@
1
+ ---
2
+ name: botrun-horse-tts-format
3
+ description: 零框架 TTS 適配 — 回答限制 500 字以內、禁止 Markdown、口語化語氣、適配語音合成 Avatar 播報
4
+ ---
5
+
6
+ 最後回答不要超過 500 字。不要 Markdown。是口語,口語聲音的回答方式。
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: botrun-horse-watermelon-sqlite
3
+ description: 零框架波西瓜 SQLite 孵化 — CSV 匯入 SQLite、_meta 詮釋資料、FTS5 Trigram 全文索引、繁中英文檢索、Agentic AI 知識庫
4
+ ---
5
+
6
+ 請你幫我們做一件事,第一個是請你把這些 CSV 都放到 ./db/ 的一個 SQLite 的 database 裡面,單一檔案就好。這是第一步驟。然後下一步驟請你製作一個 _meta,就是詮釋資料。這個裡面是未來小模型只要 SELECT * FROM _meta,它就要可以去相關的 schema 有哪些東西,要能夠一次性地讓模型理解要怎麼 SELECT 的技巧跟技術。好,然後再接下來,我們這個是一個 Agentic AI 的系統,所以你幫忙孵這個 SQLite,我們叫它波西瓜。孵化出來以後,未來我們還會增加一些網路上關於淨零的新聞,零碳排的那種淨零綠生活、淨零時代、綠色技能,這些新聞可能到時候部長問答的時候會用到,所以你也幫我做一個簡單的表,裡面先幫我上網抓一些 2026 年 2 月或 3 月的新聞,抓個 10 個好了,把它先當個 sample 放進去。
7
+
8
+ 未來用的時候,我們會希望用 Trigram,T-R-I-G-R-A-M,這個 Trigram 會建立一個 Index 是 FTS5 的 Index。建立好以後,就可以透過全文檢索把使用者自然語言提問在裡面檢索出相對應的內容給大模型回答了。英文要能夠檢索,所以你的 Index 要有英文的檢索能力。然後 Index 要有 Trigram 的檢索能力,因為是繁體中文。這些都建好以後你就會有一個 SQLite 了。你幫我先把這個 SQLite 建立起來以後,我們要準備用 Agentic AI 對它做掃描測試,謝謝。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botrun-horse",
3
- "version": "2.30.0",
3
+ "version": "2.31.0",
4
4
  "description": "多專案文件處理系統 CLI — AI 技能管理、文件擷取、公文撰寫",
5
5
  "type": "module",
6
6
  "bin": {