c152f5fc1d
kbdb-base 8.P0:scheduled.ts cron 每分鐘 KV list → 單一 key get(lib/cron-index.ts); webhooks-named 維護單 key + 一次性 migrate-cron-index;acr update 自動遷移。1440 list/日 → 0。 self-hosted-init §7.8 onboarding: P0 init 偵測+裝完驗收(lib/preflight.ts,pip 式,冪等) P1 acr whoami(+--json)+ MCP arcrun_whoami(AI 不繞 CLI 猜帳號) P2 mcp-setup 寫完印「請重啟 client」 P3(部分)repo .env.example 範本(每格白話說明、值留空)+ llms.txt 教 AI 幫用戶 cp 建 .env Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
194 lines
8.2 KiB
JavaScript
194 lines
8.2 KiB
JavaScript
#!/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 { 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-hosted:CF Account ID(非互動;亦可用 CLOUDFLARE_ACCOUNT_ID env)')
|
||
.option('--api-token <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 <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('刪除 recipe(canonical_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 list
|
||
program
|
||
.command('list')
|
||
.description('列出 CF KV 中所有已部署的 workflow')
|
||
.action(() => cmdList());
|
||
|
||
// acr logs <workflow_name>
|
||
program
|
||
.command('logs <workflow>')
|
||
.description('顯示 workflow 最近執行記錄')
|
||
.action((workflow: string) => cmdLogs(workflow));
|
||
|
||
// acr update(self-hosted:拉新 release 重新部署零件/引擎)
|
||
program
|
||
.command('update')
|
||
.description('self-hosted:拉新 release 並重新部署到你的 Cloudflare')
|
||
.action(() => cmdUpdate());
|
||
|
||
// acr install-harness(把 arcrun 的 CC harness 裝進當前專案)
|
||
program
|
||
.command('install-harness')
|
||
.description('把 arcrun 的 CC harness(mindset/提醒/防做歪 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);
|