Files
Arcrun/cli/src/commands/push.ts
T
Claude 2707fca32b feat(arcrun): implement arcrun MVP — open-source AI workflow engine
Phase 1-5 complete per .agents/specs/u6u-core-mvp/:

**Phase 1 — Cherry-pick & cleanup**
- Create arcrun/ from cypher-executor, credentials, builtins, registry
- Remove 9 InkStone Service Bindings (KBDB, REGISTRY, CLINIC_*, AICEO, MINI_ME)
- Rewrite component-loader: 3-layer (builtin → WASM_BUCKET R2 → error)
- Remove autoPublishMissing.ts, proxy.ts (AICEO), execution-logger.ts (KBDB)
- Clean all KV namespace IDs and InkStone internal URLs from config files

**Phase 2 — contract.yaml completeness**
- Add credentials_required to gmail, google_sheets, telegram, line_notify
- Add config_example to all 21 components with annotated field descriptions

**Phase 3 — Credential injection**
- Add credential-injector.ts: AES-GCM decrypt from CREDENTIALS_KV
- Integrate into GraphExecutor before WASM execution
- Structured errors with repair instructions when credential missing

**Phase 4 — CLI (acr)**
- cli/package.json: arcrun package, bin: acr, deps: commander/js-yaml/chalk/ora
- 8 commands: init, creds push, push, run, validate, parts, list, logs
- Standard mode: writes directly to user's CF KV via CF REST API
- acr init: interactive setup with arcrun.dev API Key registration

**Phase 5 — Open source release prep**
- README.md: 5-minute quickstart, component table, workflow YAML syntax
- CONTRIBUTING.md: TinyGo dev env, component scaffolding, submission flow
- Security audit: no InkStone internal URLs/IDs in committed files
- .gitignore: exclude credentials.yaml, .wrangler, *.wasm

https://claude.ai/code/session_01BnCdSLVH8tUed9VrrPavgT
2026-04-16 04:06:25 +00:00

102 lines
3.6 KiB
TypeScript
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.
/**
* acr push <workflow.yaml>
* 解析三元組,轉成執行圖,直接寫入用戶的 USER_KVkey = workflow:{name}
*/
import chalk from 'chalk';
import ora from 'ora';
import { loadConfig, getCypherExecutorUrl } from '../lib/config.js';
import { CfKvClient } from '../lib/cf-api.js';
import { loadWorkflowYaml, parseTriplets, validateRelations } from '../lib/yaml-parser.js';
export async function cmdPush(filePath: string): Promise<void> {
const config = loadConfig();
const spinner = ora('解析 workflow.yaml').start();
let workflow;
try {
workflow = loadWorkflowYaml(filePath);
spinner.text = `驗證三元組(${workflow.flow.length} 條)`;
const triplets = parseTriplets(workflow.flow);
validateRelations(triplets);
spinner.succeed(`解析完成:${workflow.name}${triplets.length} 條三元組)`);
} catch (e) {
spinner.fail(chalk.red(`解析失敗:${e instanceof Error ? e.message : e}`));
process.exit(1);
}
// POST 到 cypher-executor 取得執行圖
const executorUrl = getCypherExecutorUrl(config);
const triplets = parseTriplets(workflow.flow);
const searchSpinner = ora(`向 ${executorUrl} 解析執行圖`).start();
let graph: unknown;
try {
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (config.api_key) headers['X-Arcrun-API-Key'] = config.api_key;
const res = await fetch(`${executorUrl}/cypher/search`, {
method: 'POST',
headers,
body: JSON.stringify({ triplets: workflow.flow }),
});
if (!res.ok) {
const err = await res.text();
searchSpinner.fail(chalk.red(`執行圖解析失敗(${res.status}):${err.slice(0, 200)}`));
process.exit(1);
}
const data = await res.json() as { cypher: unknown; missing: string[] };
if (data.missing.length > 0) {
searchSpinner.fail(chalk.red(`以下零件不存在:${data.missing.join(', ')}\n執行 acr parts 查看可用零件。`));
process.exit(1);
}
graph = data.cypher;
searchSpinner.succeed('執行圖解析完成');
} catch (e) {
searchSpinner.fail(chalk.red(`網路錯誤:${e instanceof Error ? e.message : e}`));
process.exit(1);
}
// 寫入 USER_KVkey = workflow:{name}
const namespaceId = config.mode === 'standard'
? config.user_kv_namespace_id!
: config.webhooks_kv_namespace_id!;
if (!namespaceId || !config.cloudflare_account_id || !config.cf_api_token) {
console.error(chalk.red('缺少 KV 設定,請執行 acr init'));
process.exit(1);
}
const kv = new CfKvClient({
accountId: config.cloudflare_account_id,
namespaceId,
apiToken: config.cf_api_token,
});
const kvSpinner = ora('寫入 workflow 至 CF KV').start();
try {
const workflowDef = {
name: workflow.name,
description: workflow.description ?? '',
graph,
config: workflow.config ?? {},
created_at: new Date().toISOString(),
};
await kv.put(`workflow:${workflow.name}`, JSON.stringify(workflowDef));
kvSpinner.succeed(chalk.green(`✓ workflow "${workflow.name}" 已寫入你的 CF KV`));
} catch (e) {
kvSpinner.fail(chalk.red(`KV 寫入失敗:${e instanceof Error ? e.message : e}`));
process.exit(1);
}
const executorBase = getCypherExecutorUrl(config);
const webhookUrl = `${executorBase}/webhooks/${workflow.name}`;
console.log(chalk.bold(`\n Webhook URL${chalk.cyan(webhookUrl)}`));
if (config.api_key) {
console.log(chalk.gray(` (使用時需帶 X-Arcrun-API-Key: ${config.api_key.slice(0, 8)}...\n`));
}
console.log(` 執行:${chalk.cyan(`acr run ${workflow.name}`)}\n`);
}