Files
Arcrun/cli/src/index.ts
T
Claude 1d19d46161 feat(credentials): T8 回填端點 + T9 治理端點/CLI (credential-store-migration)
- POST /credentials/migrate-to-workers-secrets:舊 KV credential 逐筆解密回填 D1+Workers
  Secrets,冪等可審,重用 wasi-shim 唯一合法 crypto_decrypt 呼叫點
- GET /credentials 改讀 D1(與既有 /credentials/catalog 共用查詢);DELETE 改為新家優先、
  舊 KV fallback,避免孤兒資料
- acr creds list/replace/delete 三支 CLI 薄殼指令;順手修好過期的 acr creds push(舊
  client 端加密格式已被 T5 取代)
- 新增 cypher-executor/tests/credentials.test.ts + D1 test fixture

T6/T7(讀取/注入路徑、雙讀 fallback)需要重新編譯 registry/components/auth_static_key
的 TinyGo WASM,本環境無 tinygo 且 proxy 擋 github.com 下載,卡在工具鏈缺口,詳細分析
記錄在 credential-store-migration.md。

端到端驗證:部署到 leo21c 帳號真實跑過 GET/POST/DELETE 三分支 + migrate 端點(對真實
既存的兩筆 credential 跑,發現 cypher-executor 自己的 ENCRYPTION_KEY secret 疑似為空,
誠實記錄為待 leo/總管裁決的不可逆風險項,未擅自重設)。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 23:00:07 +00:00

256 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* arcrun CLI — acr
* AI Workflow CLI for Cloudflare Workers + WASM
*
* 安裝:npm i -g arcrun
* 使用:acr <指令>
*/
import { Command } from 'commander';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { cmdInit } from './commands/init.js';
import { cmdConfig } from './commands/config.js';
import { cmdWhoami } from './commands/whoami.js';
import { cmdCredsPush, cmdCredsList, cmdCredsReplace, cmdCredsDelete } from './commands/creds.js';
import { cmdPush } from './commands/push.js';
import { cmdRun } from './commands/run.js';
import { cmdValidate } from './commands/validate.js';
import { cmdParts, cmdPartsScaffold, cmdPartsPublish } from './commands/parts.js';
import { cmdRecipePush, cmdRecipeList, cmdRecipeDelete, cmdRecipeSearch, cmdRecipePull, cmdRecipeSubmitP } from './commands/recipe.js';
import { cmdList } from './commands/list.js';
import { cmdSearch } from './commands/search.js';
import { cmdLogs } from './commands/logs.js';
import { cmdUpdate } from './commands/update.js';
import { cmdInstallHarness } from './commands/install-harness.js';
import { cmdMcpSetup } from './commands/mcp-setup.js';
import {
cmdKbdbTemplateCreate,
cmdKbdbTemplateList,
cmdKbdbRecordCreate,
cmdKbdbRecordGet,
cmdKbdbQuery,
cmdKbdbSearch,
} from './commands/kbdb.js';
import { cmdAuthRecipeList, cmdAuthRecipeInfo, cmdAuthRecipeScaffold } from './commands/auth-recipe.js';
const program = new Command();
// 版本從 package.json 動態讀(dist/index.js 旁的 ../package.json),不 hardcode → 永不漂移。
// 之前 hardcode '1.1.0' 與 package.json '1.2.0' 不一致,正是「忘了改」的反例。
function readVersion(): string {
try {
const here = dirname(fileURLToPath(import.meta.url));
return (JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8')) as { version?: string }).version ?? '0.0.0';
} catch {
return '0.0.0';
}
}
program
.name('acr')
.description('arcrun — AI Workflow CLI for Cloudflare Workers + WASM')
.version(readVersion());
// acr init [--self-hosted] [--account-id <id>] [--api-token <token>]
program
.command('init')
.description('互動式初始化設定(建立 ~/.arcrun/config.yaml')
.option('--local', '本機模式:不需要 Cloudflare 帳號,直接在本機測試 workflow')
.option('--self-hosted', '完全 Self-hosted 模式:自行部署所有 Cloudflare Worker')
.option('--account-id <id>', 'self-hostedCF Account ID(非互動;亦可用 CLOUDFLARE_ACCOUNT_ID env')
.option('--api-token <token>', 'self-hostedCF API Token(非互動;亦可用 CLOUDFLARE_API_TOKEN env')
.action((options: { local?: boolean; selfHosted?: boolean; accountId?: string; apiToken?: string }) =>
cmdInit(options),
);
// acr config [--where]:印出目前生效的設定與每個值的來源層(env / 專案層 / 全域)
program
.command('config')
.description('顯示目前生效的設定與來源層(避免在錯的資料夾用錯帳號)')
.option('--where', '顯示每個設定值來自哪一層(env > 專案層 .arcrun.yaml > 全域)')
.action((options: { where?: boolean }) => cmdConfig(options));
// acr whoami [--json]:一眼看當前身份(mode / 連哪台 cypher / 帳號來源層)。§7.8 P1 D2 修法。
program
.command('whoami')
.description('顯示目前生效的身份(帳號、連哪台 cypher、來源層)——AI 別自己 curl 猜帳號')
.option('--json', '結構化輸出(給 AI / 腳本讀取)')
.action((options: { json?: boolean }) => cmdWhoami(options));
// acr creds push/list/replace/delete — credential-store-migration T9(治理端點 CLI 薄殼)
const credsCmd = program.command('creds').description('Credential 管理(D19:只能看目錄/replace/delete,讀不回值)');
credsCmd
.command('push [file]')
.description('批次上傳 credentials.yaml(明文經 TLS,值存進你的 Workers Secrets,不經過 arcrun.dev')
.action((file: string) => cmdCredsPush(file ?? 'credentials.yaml'));
credsCmd
.command('list')
.description('列出已存的 credential 目錄(name/service/sensitivity/last_used,不含值)')
.action(() => cmdCredsList());
credsCmd
.command('replace <name> <value>')
.description('整筆覆寫一個 credential 的值(只能整筆換,不能局部編輯)')
.option('--service <service>', '對應 service 名(如 telegram')
.option('--sensitivity <level>', 'standard | high')
.action((name: string, value: string, options: { service?: string; sensitivity?: string }) =>
cmdCredsReplace(name, value, options));
credsCmd
.command('delete <name>')
.description('刪除一個 credential(目錄 row + Workers Secret 本體)')
.action((name: string) => cmdCredsDelete(name));
// acr push <workflow.yaml>
program
.command('push <file>')
.description('解析 workflow.yaml 並部署至你的 CF KV')
.action((file: string) => cmdPush(file));
// acr run <workflow_name> [--input key=value...]
program
.command('run <workflow>')
.description('執行指定 workflow')
.option('-i, --input <pairs...>', 'input 參數(格式:key=value')
.action((workflow: string, options: { input?: string[] }) => cmdRun(workflow, options));
// acr validate <workflow.yaml>
program
.command('validate <file>')
.description('執行前驗證 workflow.yaml(格式、關係詞、零件存在性、credentials')
.option('--offline', '離線模式:跳過零件存在性與 credentials 的遠端檢查')
.action((file: string, options: { offline?: boolean }) => cmdValidate(file, options));
// acr parts
// acr parts scaffold <component>
// acr parts publish <component> [--status <submission_id>]
const partsCmd = program.command('parts').description('零件庫管理');
partsCmd
.action(() => cmdParts());
partsCmd
.command('scaffold <component>')
.description('輸出零件的 config 範本(可直接貼入 workflow.yaml')
.action((component: string) => cmdPartsScaffold(component));
partsCmd
.command('publish <component-dir>')
.description('提交零件至 arcrun.dev 公眾 registry')
.option('--status <submission_id>', '查詢提交審核進度')
.action((dir: string, options: { status?: string }) => cmdPartsPublish(dir, options));
// acr recipe push / list / delete
const recipeCmd = program.command('recipe').description('API Recipe 管理');
recipeCmd
.command('push <file>')
.description('上傳 recipe YAML 到 arcrun.dev(不需要 deploy Worker')
.action((file: string) => cmdRecipePush(file));
recipeCmd
.command('list')
.description('列出已上傳的 recipe')
.action(() => cmdRecipeList());
recipeCmd
.command('delete <id>')
.description('刪除 recipecanonical_id 或 rec_hash')
.action((id: string) => cmdRecipeDelete(id));
// 公庫互動(kbdb-base §7.5
recipeCmd
.command('search <query>')
.description('搜尋公庫 recipe(同名可多作者,附市場數據)')
.action((query: string) => cmdRecipeSearch(query));
recipeCmd
.command('pull <canonical_id>')
.description('從公庫取一份 recipe 寫進自己私庫')
.option('--author <name>', '指定作者版本(不指定取市場最佳)')
.action((canonicalId: string, opts: { author?: string }) => cmdRecipePull(canonicalId, opts.author));
recipeCmd
.command('submit-p <canonical_id>')
.description('把私庫某 recipe 投稿到公庫(新增作者版本,需暴露同意)')
.option('--author <name>', '署名作者(預設用 recipe 既有 author')
.action((canonicalId: string, opts: { author?: string }) => cmdRecipeSubmitP(canonicalId, opts.author));
// acr auth-recipe list / info / scaffold
const authRecipeCmd = program.command('auth-recipe').description('第三方服務認證 Recipe(新增服務整合)');
authRecipeCmd
.command('list')
.description('列出所有平台預建的服務整合(Notion、Slack、GitHub 等)')
.action(() => cmdAuthRecipeList());
authRecipeCmd
.command('info <service>')
.description('顯示服務 recipe 詳情(需要哪些 credential')
.action((service: string) => cmdAuthRecipeInfo(service));
authRecipeCmd
.command('scaffold <service>')
.description('輸出 credentials.yaml 範本 + workflow.yaml 使用範例')
.action((service: string) => cmdAuthRecipeScaffold(service));
// acr kbdb — KBDB 資料層薄殼(kbdb-base 9.2,透過 cypher KBDB proxy;與 MCP kbdb_* 同能力)
const kbdbCmd = program.command('kbdb').description('KBDB 資料層(template/record/query/search;不建表、不寫 SQL');
const kbdbTemplateCmd = kbdbCmd.command('template').description('template = 虛擬表定義(name + slots');
kbdbTemplateCmd
.command('create <name>')
.description('建一個 template(虛擬表定義),如 --slots name,email,phone')
.requiredOption('--slots <list>', '欄位名清單,逗號分隔,如 name,email,phone')
.action((name: string, opts: { slots: string }) => cmdKbdbTemplateCreate(name, opts));
kbdbTemplateCmd
.command('list')
.description('列出所有 template')
.action(() => cmdKbdbTemplateList());
const kbdbRecordCmd = kbdbCmd.command('record').description('record = 依 template 填的一筆資料');
kbdbRecordCmd
.command('create <template>')
.description('填一筆 record,如 --values name=Leo --values email=x@y.com(可重複)')
.option('--values <pair...>', 'slot=內容(可重複)')
.action((template: string, opts: { values?: string[] }) => cmdKbdbRecordCreate(template, opts));
kbdbRecordCmd
.command('get <record_id>')
.description('取單筆 record 全文')
.action((recordId: string) => cmdKbdbRecordGet(recordId));
kbdbCmd
.command('query <template>')
.description('列某 template 下本租戶的所有 record')
.action((template: string) => cmdKbdbQuery(template));
kbdbCmd
.command('search <q>')
.description('關鍵字搜尋本租戶內容(LIKE,基本盤)')
.action((q: string) => cmdKbdbSearch(q));
// acr list
program
.command('list')
.description('列出 CF KV 中所有已部署的 workflow')
.action(() => cmdList());
// acr search <term> — 跨類統一搜尋(component / recipe / auth-recipe / workflow),免先選表
program
.command('search <term>')
.description('跨類搜尋能力(零件/recipe/auth-recipe/workflow),一次掃全部、依類別回 counts+命中')
.action((term: string) => cmdSearch(term));
// acr logs <workflow_name>
program
.command('logs <workflow>')
.description('顯示 workflow 最近執行記錄')
.action((workflow: string) => cmdLogs(workflow));
// acr updateself-hosted:拉新 release 重新部署零件/引擎)
program
.command('update')
.description('self-hosted:拉新 release 並重新部署到你的 Cloudflare')
.option('--force', '強制重部所有 worker(忽略未變動跳過快取)')
.action((opts: { force?: boolean }) => cmdUpdate({ force: opts.force }));
// acr install-harness(把 arcrun 的 CC harness 裝進當前專案)
program
.command('install-harness')
.description('把 arcrun 的 CC harnessmindset/提醒/防做歪 hook/指令)裝進當前專案')
.action(() => cmdInstallHarness());
// acr mcp-setup(依 config 解析的 mcp_url 寫專案 .mcp.json,讓 Claude Code 連對的 MCP
program
.command('mcp-setup')
.description('在目前資料夾寫 .mcp.json 連對的 arcrun MCP(依 config 的 mcp_url;接案切資料夾自動切)')
.action(() => cmdMcpSetup());
program.parse(process.argv);