feat(cli): acr search 跨類統一搜尋 + acr parts 去 hardcode recipe(issue #13 根治)
leo 2026-06-29 重構根因:「telegram 與 google 不一致」其實是查錯表——舊世界要找一個能力 必須先決定它是 component/recipe/workflow 才查對清單,但查到答案前根本不知道屬哪類 → 「先知道分類」成了查詢前置,倒因為果(leo:連我都會查錯表)。這是搜尋設計缺陷,非粗心。 兩部分互補修法: 1. acr parts 清理(W3.1/W3.3):移除 5 個 hardcode 的 recipe(gmail_send/google_sheets_append/ telegram_send/line_notify_send/notion)——它們是 recipe(動態,存 store)不是零件(component)。 只留真 WASM 零件。檔頭寫死分流原則 + 移除處留註解防再 hardcode。footer 改指向動態清單 + acr search。 2. acr search <term> 新增(W3.2,正向修法):cli/src/commands/search.ts。fan-out 4 來源—— component(靜態 BUILTIN_COMPONENTS) + recipe(GET /recipes) + auth-recipe(GET /auth-recipes) + workflow(GET /webhooks/named),依類別回 counts+命中。各來源獨立 try(離線降級指路、不連坐)。 查的人不必先選表 → 結構性消除「查錯表」。 薄殼合規(rule 07 §2):search 只 fan-out 既有清單端點 + 過濾 + 分組 + 印出(介面層暴露/格式化), 不在介面層拼裝能力。component 靜態(PR-only)、recipe/auth/workflow 動態(store) 分流明確、不再 conflate。 實證(對 leo21c 跑,read-only GET): - acr search telegram → component:0 recipe:1 auth-recipe:0 workflow:1(一眼看出 telegram 是 recipe) - acr parts → 只剩 http_request 等真零件,5 個 recipe 已不在 - tsc 全綠、build 通過 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+23
-71
@@ -1,7 +1,17 @@
|
||||
/**
|
||||
* acr parts — 列出所有可用零件(內建清單,不依賴 registry.arcrun.dev)
|
||||
* acr parts — 列出所有可用「零件(component)」(內建清單,不依賴 registry.arcrun.dev)
|
||||
* acr parts scaffold <component> — 輸出 config 範本(可直接貼入 workflow.yaml)
|
||||
* acr parts publish <component> — 提交零件至公眾 registry(Phase 5,封測後)
|
||||
*
|
||||
* ⚠️ 分類原則(2026-06-29,issue #13 / component-gatekeeping W3):
|
||||
* - **零件(component)= 靜態清單**:WASM,只能走 GitHub PR + 人 merge 新增(mindset §4 人類閘門),
|
||||
* 固定慢增 → 用 BUILTIN_COMPONENTS hardcode 反映真實,正確。
|
||||
* - **recipe / auth-recipe / workflow = 動態,存在 store**:任何人 `acr recipe push` 即新增 →
|
||||
* **絕不可 hardcode 在這裡**(會「submitted = invisible」+ 誤導查錯表)。它們各有動態清單:
|
||||
* recipe → `acr recipe list`(GET /recipes)|auth-recipe → `acr auth-recipe list`(GET /auth-recipes)|workflow → `acr list`(GET /webhooks/named)
|
||||
* - **跨類找東西用 `acr search <term>`**(fan-out 上述 4 個來源,不必先知道它是哪一類)。
|
||||
* 歷史教訓:本檔曾把 gmail_send/telegram_send/notion 等 5 個 recipe hardcode 進零件清單 →
|
||||
* 誤導「telegram 是零件 / 走不同機制」。已移除(W3.1)。
|
||||
*/
|
||||
import { readFileSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
@@ -25,7 +35,7 @@ interface ComponentDef {
|
||||
credentials_required?: CredentialRequirement[];
|
||||
}
|
||||
|
||||
const BUILTIN_COMPONENTS: ComponentDef[] = [
|
||||
export const BUILTIN_COMPONENTS: ComponentDef[] = [
|
||||
// ── 控制類(Logic) ────────────────────────────────────────────────────────
|
||||
{
|
||||
canonical_id: 'if_control',
|
||||
@@ -193,72 +203,9 @@ const BUILTIN_COMPONENTS: ComponentDef[] = [
|
||||
body:
|
||||
key: "{{value}}"`,
|
||||
},
|
||||
{
|
||||
canonical_id: 'gmail_send',
|
||||
display_name: 'Gmail Send',
|
||||
category: 'api',
|
||||
description: '透過 recipe 寄送 Gmail(recipe gmail_send,auth: google service_account)。注意:Gmail 讀取尚無對應 recipe(待 seed 補 gmail_list)。',
|
||||
config_example:
|
||||
` # 寄信用內建 recipe gmail_send;body 帶 raw(base64url MIME)
|
||||
mail_node:
|
||||
component: gmail_send
|
||||
method: POST
|
||||
body:
|
||||
raw: "{{mime_base64url}}"`,
|
||||
},
|
||||
{
|
||||
canonical_id: 'google_sheets_append',
|
||||
display_name: 'Google Sheets Append',
|
||||
category: 'api',
|
||||
description: '透過 recipe 追加一列到 Sheets(recipe google_sheets_append;讀取用 google_sheets_read。auth: google service_account)',
|
||||
config_example:
|
||||
` # 追加用內建 recipe google_sheets_append(讀取改用 google_sheets_read)
|
||||
sheet_node:
|
||||
component: google_sheets_append
|
||||
method: POST
|
||||
_path: "/v4/spreadsheets/{{spreadsheet_id}}/values/Sheet1!A:B:append?valueInputOption=RAW"
|
||||
body:
|
||||
values:
|
||||
- ["{{name}}", "{{email}}"]`,
|
||||
},
|
||||
{
|
||||
canonical_id: 'telegram_send',
|
||||
display_name: 'Telegram Send',
|
||||
category: 'api',
|
||||
description: '透過 recipe 發送 Telegram 訊息(recipe telegram_send,auth: static_key,token 注入 URL path)',
|
||||
config_example:
|
||||
` # 內建 recipe telegram_send(token 由 auth 注入 URL,不寫在這)
|
||||
tg_node:
|
||||
component: telegram_send
|
||||
chat_id: "123456789"
|
||||
text: "{{message}}"`,
|
||||
},
|
||||
{
|
||||
canonical_id: 'line_notify_send',
|
||||
display_name: 'LINE Notify',
|
||||
category: 'api',
|
||||
description: '透過 recipe 發送 LINE Notify 通知(recipe line_notify_send,auth: static_key Bearer)',
|
||||
config_example:
|
||||
` # 內建 recipe line_notify_send(POST notify,body form-urlencoded)
|
||||
line_node:
|
||||
component: line_notify_send
|
||||
method: POST
|
||||
body:
|
||||
message: "{{notification}}"`,
|
||||
},
|
||||
{
|
||||
canonical_id: 'notion',
|
||||
display_name: 'Notion',
|
||||
category: 'api',
|
||||
description: '透過 recipe 操作 Notion API(需先 acr recipe push)',
|
||||
config_example:
|
||||
` # 先上傳 recipe:acr recipe push notion.yaml
|
||||
notion_node:
|
||||
component: rec_xxxxxxxx # acr recipe push 後得到的 hash
|
||||
database_id: "{{db_id}}"
|
||||
properties:
|
||||
Name: "{{title}}"`,
|
||||
},
|
||||
// ⚠️ 此處曾 hardcode gmail_send / google_sheets_append / telegram_send / line_notify_send / notion
|
||||
// 這 5 個是 **recipe(動態,存 store)不是零件(component)**,已於 W3.1 移除(issue #13 根治)。
|
||||
// 要找它們:`acr search <term>`(跨類)或 `acr recipe list` / `acr auth-recipe list`(動態清單)。
|
||||
];
|
||||
|
||||
// ── 指令實作 ──────────────────────────────────────────────────────────────────
|
||||
@@ -277,7 +224,7 @@ export async function cmdParts(): Promise<void> {
|
||||
grouped[comp.category].push(comp);
|
||||
}
|
||||
|
||||
console.log(chalk.bold(`\n arcrun 零件庫(${BUILTIN_COMPONENTS.length} 個內建零件)\n`));
|
||||
console.log(chalk.bold(`\n arcrun 零件庫(${BUILTIN_COMPONENTS.length} 個內建零件 / component,靜態 PR-only)\n`));
|
||||
|
||||
for (const cat of ['logic', 'data', 'ai', 'api']) {
|
||||
const comps = grouped[cat];
|
||||
@@ -294,8 +241,13 @@ export async function cmdParts(): Promise<void> {
|
||||
}
|
||||
|
||||
console.log(chalk.gray(' 使用 acr parts scaffold <component> 取得 config 範本'));
|
||||
console.log(chalk.gray(' 第三方服務整合(Notion/Slack/GitHub 等):acr auth-recipe list'));
|
||||
console.log(chalk.gray(' API 整合類若需打自訂服務,請用 acr recipe push 建立 recipe\n'));
|
||||
console.log('');
|
||||
console.log(chalk.bold(' 零件之外(動態,存在 store,不在上面這份靜態清單):'));
|
||||
console.log(chalk.gray(' • API recipe(打外部服務) acr recipe list'));
|
||||
console.log(chalk.gray(' • 第三方服務認證(auth-recipe) acr auth-recipe list'));
|
||||
console.log(chalk.gray(' • 已部署的 workflow acr list'));
|
||||
console.log(chalk.cyan(' • 不確定某能力是哪一類? acr search <關鍵字> ← 跨類一次搜,免先選表'));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
export async function cmdPartsScaffold(componentId: string): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user