#!/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 } 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 { 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 ] [--api-token ] program .command('init') .description('互動式初始化設定(建立 ~/.arcrun/config.yaml)') .option('--local', '本機模式:不需要 Cloudflare 帳號,直接在本機測試 workflow') .option('--self-hosted', '完全 Self-hosted 模式:自行部署所有 Cloudflare Worker') .option('--account-id ', 'self-hosted:CF Account ID(非互動;亦可用 CLOUDFLARE_ACCOUNT_ID env)') .option('--api-token ', 'self-hosted:CF 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 [credentials.yaml] const credsCmd = program.command('creds').description('Credential 管理'); credsCmd .command('push [file]') .description('加密上傳 credentials.yaml 至你的 CF KV(不經過 arcrun.dev)') .action((file: string) => cmdCredsPush(file ?? 'credentials.yaml')); // acr push program .command('push ') .description('解析 workflow.yaml 並部署至你的 CF KV') .action((file: string) => cmdPush(file)); // acr run [--input key=value...] program .command('run ') .description('執行指定 workflow') .option('-i, --input ', 'input 參數(格式:key=value)') .action((workflow: string, options: { input?: string[] }) => cmdRun(workflow, options)); // acr validate program .command('validate ') .description('執行前驗證 workflow.yaml(格式、關係詞、零件存在性、credentials)') .option('--offline', '離線模式:跳過零件存在性與 credentials 的遠端檢查') .action((file: string, options: { offline?: boolean }) => cmdValidate(file, options)); // acr parts // acr parts scaffold // acr parts publish [--status ] const partsCmd = program.command('parts').description('零件庫管理'); partsCmd .action(() => cmdParts()); partsCmd .command('scaffold ') .description('輸出零件的 config 範本(可直接貼入 workflow.yaml)') .action((component: string) => cmdPartsScaffold(component)); partsCmd .command('publish ') .description('提交零件至 arcrun.dev 公眾 registry') .option('--status ', '查詢提交審核進度') .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 ') .description('上傳 recipe YAML 到 arcrun.dev(不需要 deploy Worker)') .action((file: string) => cmdRecipePush(file)); recipeCmd .command('list') .description('列出已上傳的 recipe') .action(() => cmdRecipeList()); recipeCmd .command('delete ') .description('刪除 recipe(canonical_id 或 rec_hash)') .action((id: string) => cmdRecipeDelete(id)); // 公庫互動(kbdb-base §7.5) recipeCmd .command('search ') .description('搜尋公庫 recipe(同名可多作者,附市場數據)') .action((query: string) => cmdRecipeSearch(query)); recipeCmd .command('pull ') .description('從公庫取一份 recipe 寫進自己私庫') .option('--author ', '指定作者版本(不指定取市場最佳)') .action((canonicalId: string, opts: { author?: string }) => cmdRecipePull(canonicalId, opts.author)); recipeCmd .command('submit-p ') .description('把私庫某 recipe 投稿到公庫(新增作者版本,需暴露同意)') .option('--author ', '署名作者(預設用 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 ') .description('顯示服務 recipe 詳情(需要哪些 credential)') .action((service: string) => cmdAuthRecipeInfo(service)); authRecipeCmd .command('scaffold ') .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 ') .description('建一個 template(虛擬表定義),如 --slots name,email,phone') .requiredOption('--slots ', '欄位名清單,逗號分隔,如 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