arcrun 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/commands/creds.d.ts +2 -0
  2. package/dist/commands/creds.d.ts.map +1 -0
  3. package/dist/commands/creds.js +62 -0
  4. package/dist/commands/creds.js.map +1 -0
  5. package/dist/commands/init.d.ts +4 -0
  6. package/dist/commands/init.d.ts.map +1 -0
  7. package/dist/commands/init.js +120 -0
  8. package/dist/commands/init.js.map +1 -0
  9. package/dist/commands/list.d.ts +2 -0
  10. package/dist/commands/list.d.ts.map +1 -0
  11. package/dist/commands/list.js +61 -0
  12. package/dist/commands/list.js.map +1 -0
  13. package/dist/commands/logs.d.ts +2 -0
  14. package/dist/commands/logs.d.ts.map +1 -0
  15. package/dist/commands/logs.js +67 -0
  16. package/dist/commands/logs.js.map +1 -0
  17. package/dist/commands/parts.d.ts +6 -0
  18. package/dist/commands/parts.d.ts.map +1 -0
  19. package/dist/commands/parts.js +246 -0
  20. package/dist/commands/parts.js.map +1 -0
  21. package/dist/commands/push.d.ts +2 -0
  22. package/dist/commands/push.d.ts.map +1 -0
  23. package/dist/commands/push.js +93 -0
  24. package/dist/commands/push.js.map +1 -0
  25. package/dist/commands/run.d.ts +6 -0
  26. package/dist/commands/run.d.ts.map +1 -0
  27. package/dist/commands/run.js +60 -0
  28. package/dist/commands/run.js.map +1 -0
  29. package/dist/commands/validate.d.ts +2 -0
  30. package/dist/commands/validate.d.ts.map +1 -0
  31. package/dist/commands/validate.js +139 -0
  32. package/dist/commands/validate.js.map +1 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +77 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/lib/cf-api.d.ts +25 -0
  38. package/dist/lib/cf-api.d.ts.map +1 -0
  39. package/dist/lib/cf-api.js +89 -0
  40. package/dist/lib/cf-api.js.map +1 -0
  41. package/dist/lib/config.d.ts +17 -0
  42. package/dist/lib/config.d.ts.map +1 -0
  43. package/dist/lib/config.js +31 -0
  44. package/dist/lib/config.js.map +1 -0
  45. package/dist/lib/yaml-parser.d.ts +16 -0
  46. package/dist/lib/yaml-parser.d.ts.map +1 -0
  47. package/dist/lib/yaml-parser.js +55 -0
  48. package/dist/lib/yaml-parser.js.map +1 -0
  49. package/package.json +38 -0
@@ -0,0 +1,2 @@
1
+ export declare function cmdCredsPush(filePath: string): Promise<void>;
2
+ //# sourceMappingURL=creds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creds.d.ts","sourceRoot":"","sources":["../../src/commands/creds.ts"],"names":[],"mappings":"AAYA,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyDlE"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * acr creds push [credentials.yaml]
3
+ * 讀取 credentials.yaml,加密後上傳至用戶自己的 CF KV(key 格式:cred:{name})
4
+ * 不經過 arcrun.dev
5
+ */
6
+ import { readFileSync } from 'node:fs';
7
+ import yaml from 'js-yaml';
8
+ import chalk from 'chalk';
9
+ import ora from 'ora';
10
+ import { loadConfig } from '../lib/config.js';
11
+ import { CfKvClient, encryptCredential } from '../lib/cf-api.js';
12
+ export async function cmdCredsPush(filePath) {
13
+ const config = loadConfig();
14
+ if (!config.cloudflare_account_id || !config.cf_api_token) {
15
+ console.error(chalk.red('缺少 Cloudflare 設定,請執行 acr init'));
16
+ process.exit(1);
17
+ }
18
+ // 讀取 credentials.yaml
19
+ let creds;
20
+ try {
21
+ const raw = readFileSync(filePath, 'utf8');
22
+ creds = yaml.load(raw);
23
+ }
24
+ catch (e) {
25
+ console.error(chalk.red(`無法讀取 ${filePath}:${e instanceof Error ? e.message : e}`));
26
+ process.exit(1);
27
+ }
28
+ const entries = Object.entries(creds).filter(([, v]) => typeof v === 'string' && v.length > 0);
29
+ if (entries.length === 0) {
30
+ console.log(chalk.yellow('credentials.yaml 中沒有有效的 credential(請取消注解並填入值)'));
31
+ return;
32
+ }
33
+ // 決定要寫入哪個 KV namespace
34
+ const namespaceId = config.mode === 'standard'
35
+ ? config.user_kv_namespace_id
36
+ : config.credentials_kv_namespace_id;
37
+ if (!namespaceId) {
38
+ console.error(chalk.red('缺少 KV Namespace ID,請執行 acr init'));
39
+ process.exit(1);
40
+ }
41
+ const kv = new CfKvClient({
42
+ accountId: config.cloudflare_account_id,
43
+ namespaceId,
44
+ apiToken: config.cf_api_token,
45
+ });
46
+ // 加密金鑰(若無則用 dev 模式 base64)
47
+ const encryptionKey = process.env.ARCRUN_ENCRYPTION_KEY ?? '';
48
+ console.log(chalk.bold(`\n 上傳 ${entries.length} 個 credentials 至你的 CF KV\n`));
49
+ for (const [name, value] of entries) {
50
+ const spinner = ora(` ${name}`).start();
51
+ try {
52
+ const encrypted = await encryptCredential(String(value), encryptionKey);
53
+ await kv.put(`cred:${name}`, encrypted);
54
+ spinner.succeed(chalk.green(` ✓ ${name} 已加密上傳至你的 CF KV`));
55
+ }
56
+ catch (e) {
57
+ spinner.fail(chalk.red(` ✗ ${name} 失敗:${e instanceof Error ? e.message : e}`));
58
+ }
59
+ }
60
+ console.log(chalk.gray('\n 你的 credential 存在你自己的 CF KV,arcrun 不會儲存它們。\n'));
61
+ }
62
+ //# sourceMappingURL=creds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creds.js","sourceRoot":"","sources":["../../src/commands/creds.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function cmdInit(options: {
2
+ selfHosted?: boolean;
3
+ }): Promise<void>;
4
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAkBA,wBAAsB,OAAO,CAAC,OAAO,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAc9E"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * acr init — 互動式初始化設定
3
+ * 詢問 CF Account ID、KV namespace、API Token、email,
4
+ * 呼叫 arcrun.dev 取得 API Key,寫入 ~/.arcrun/config.yaml
5
+ */
6
+ import { createInterface } from 'node:readline/promises';
7
+ import { writeFileSync, existsSync, readFileSync, appendFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import chalk from 'chalk';
10
+ import { saveConfig } from '../lib/config.js';
11
+ const ARCRUN_REGISTER_URL = 'https://api.arcrun.dev/register';
12
+ async function prompt(rl, question) {
13
+ const answer = await rl.question(chalk.cyan(`? ${question}: `));
14
+ return answer.trim();
15
+ }
16
+ export async function cmdInit(options) {
17
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
18
+ console.log(chalk.bold('\n arcrun 初始化設定\n'));
19
+ try {
20
+ if (options.selfHosted) {
21
+ await initSelfHosted(rl);
22
+ }
23
+ else {
24
+ await initStandard(rl);
25
+ }
26
+ }
27
+ finally {
28
+ rl.close();
29
+ }
30
+ }
31
+ async function initStandard(rl) {
32
+ console.log(chalk.gray(' Standard 模式:使用 arcrun.dev 的執行引擎,credential 存在你自己的 CF KV\n'));
33
+ const accountId = await prompt(rl, '你的 Cloudflare Account ID');
34
+ const kvNamespaceId = await prompt(rl, 'USER_KV Namespace ID(先至 CF Dashboard 建立一個 KV 後貼上)');
35
+ const cfApiToken = await prompt(rl, 'CF API Token(只需 KV Edit 權限,供 acr 讀寫你的 KV)');
36
+ const email = await prompt(rl, 'Email(取得 arcrun.dev API Key)');
37
+ process.stdout.write(chalk.gray('\n → 向 arcrun.dev 取得 API Key...'));
38
+ let apiKey;
39
+ try {
40
+ const res = await fetch(ARCRUN_REGISTER_URL, {
41
+ method: 'POST',
42
+ headers: { 'Content-Type': 'application/json' },
43
+ body: JSON.stringify({ email }), // CF API Token 永遠不離開本機
44
+ });
45
+ if (!res.ok) {
46
+ const err = await res.text();
47
+ throw new Error(`API Key 取得失敗(${res.status}):${err}`);
48
+ }
49
+ const data = await res.json();
50
+ apiKey = data.api_key;
51
+ console.log(chalk.green(' ✓'));
52
+ }
53
+ catch (e) {
54
+ console.log(chalk.yellow(' ✗(離線模式,請稍後執行 acr init 重試)'));
55
+ apiKey = '';
56
+ }
57
+ const config = {
58
+ mode: 'standard',
59
+ cloudflare_account_id: accountId,
60
+ user_kv_namespace_id: kvNamespaceId,
61
+ cf_api_token: cfApiToken,
62
+ api_key: apiKey,
63
+ };
64
+ saveConfig(config);
65
+ // 建立空白 credentials.yaml
66
+ createCredentialsYamlIfMissing();
67
+ console.log(chalk.green('\n ✓ 設定完成 → ~/.arcrun/config.yaml'));
68
+ if (apiKey) {
69
+ console.log(chalk.green(` ✓ API Key:${apiKey.slice(0, 8)}...(已安全儲存)`));
70
+ }
71
+ console.log(chalk.green(' ✓ 建立 credentials.yaml(已加入 .gitignore)\n'));
72
+ console.log(chalk.gray(' 你的 credential 與 workflow 存在你自己的 CF KV,arcrun 不會儲存它們。\n'));
73
+ console.log(' 下一步:');
74
+ console.log(chalk.cyan(' acr creds push credentials.yaml') + ' # 上傳加密 credentials');
75
+ console.log(chalk.cyan(' acr push workflow.yaml') + ' # 部署 workflow');
76
+ console.log(chalk.cyan(' acr run <workflow_name>') + ' # 執行 workflow\n');
77
+ }
78
+ async function initSelfHosted(rl) {
79
+ console.log(chalk.gray(' Self-hosted 模式:自行部署所有 Worker 到你的 Cloudflare 帳號\n'));
80
+ const accountId = await prompt(rl, '你的 Cloudflare Account ID');
81
+ const cypherUrl = await prompt(rl, 'Cypher Executor URL(部署後的 workers.dev URL)');
82
+ const webhooksKvId = await prompt(rl, 'WEBHOOKS KV Namespace ID');
83
+ const credentialsKvId = await prompt(rl, 'CREDENTIALS_KV Namespace ID');
84
+ const wasmBucket = await prompt(rl, 'WASM_BUCKET 名稱');
85
+ const cfApiToken = await prompt(rl, 'CF API Token(KV Edit 權限)');
86
+ const config = {
87
+ mode: 'self-hosted',
88
+ cloudflare_account_id: accountId,
89
+ cypher_executor_url: cypherUrl,
90
+ webhooks_kv_namespace_id: webhooksKvId,
91
+ credentials_kv_namespace_id: credentialsKvId,
92
+ wasm_bucket: wasmBucket,
93
+ cf_api_token: cfApiToken,
94
+ multi_tenant: false,
95
+ };
96
+ saveConfig(config);
97
+ createCredentialsYamlIfMissing();
98
+ console.log(chalk.green('\n ✓ 設定完成 → ~/.arcrun/config.yaml'));
99
+ console.log(chalk.green(' ✓ 建立 credentials.yaml\n'));
100
+ }
101
+ function createCredentialsYamlIfMissing() {
102
+ const credPath = join(process.cwd(), 'credentials.yaml');
103
+ if (!existsSync(credPath)) {
104
+ writeFileSync(credPath, '# arcrun credentials — 不要提交至 git!\n' +
105
+ '# 執行 acr creds push 上傳加密後的 credential 到你的 CF KV\n\n' +
106
+ '# gmail_token: "your-google-oauth-token"\n' +
107
+ '# telegram_bot_token: "your-telegram-bot-token"\n' +
108
+ '# google_oauth: "your-google-oauth-token"\n' +
109
+ '# line_token: "your-line-notify-token"\n', 'utf8');
110
+ }
111
+ // 確保 .gitignore 排除 credentials.yaml
112
+ const gitignorePath = join(process.cwd(), '.gitignore');
113
+ if (existsSync(gitignorePath)) {
114
+ const content = readFileSync(gitignorePath, 'utf8');
115
+ if (!content.includes('credentials.yaml')) {
116
+ appendFileSync(gitignorePath, '\ncredentials.yaml\n');
117
+ }
118
+ }
119
+ }
120
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdList(): Promise<void>;
2
+ //# sourceMappingURL=list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAQA,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CA2D7C"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * acr list — 列出 USER_KV 中所有已上傳的 workflow
3
+ */
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { loadConfig } from '../lib/config.js';
7
+ import { CfKvClient } from '../lib/cf-api.js';
8
+ export async function cmdList() {
9
+ const config = loadConfig();
10
+ if (!config.cloudflare_account_id || !config.cf_api_token) {
11
+ console.error(chalk.red('缺少 Cloudflare 設定,請執行 acr init'));
12
+ process.exit(1);
13
+ }
14
+ const namespaceId = config.mode === 'standard'
15
+ ? config.user_kv_namespace_id
16
+ : config.webhooks_kv_namespace_id;
17
+ if (!namespaceId) {
18
+ console.error(chalk.red('缺少 KV Namespace ID,請執行 acr init'));
19
+ process.exit(1);
20
+ }
21
+ const kv = new CfKvClient({
22
+ accountId: config.cloudflare_account_id,
23
+ namespaceId,
24
+ apiToken: config.cf_api_token,
25
+ });
26
+ const spinner = ora('讀取 workflow 清單').start();
27
+ try {
28
+ const keys = await kv.list('workflow:');
29
+ spinner.stop();
30
+ if (keys.length === 0) {
31
+ console.log(chalk.yellow('\n 沒有已部署的 workflow。執行 acr push <workflow.yaml> 部署第一個。\n'));
32
+ return;
33
+ }
34
+ console.log(chalk.bold(`\n 已部署 ${keys.length} 個 workflow\n`));
35
+ for (const key of keys) {
36
+ const name = key.name.replace('workflow:', '');
37
+ // 嘗試讀取 workflow 定義取得 created_at
38
+ try {
39
+ const raw = await kv.get(key.name);
40
+ if (raw) {
41
+ const def = JSON.parse(raw);
42
+ const date = def.created_at ? new Date(def.created_at).toLocaleString('zh-TW') : '未知';
43
+ const desc = def.description ? chalk.gray(` — ${def.description}`) : '';
44
+ console.log(` • ${chalk.cyan(name.padEnd(25))} ${date}${desc}`);
45
+ }
46
+ else {
47
+ console.log(` • ${chalk.cyan(name)}`);
48
+ }
49
+ }
50
+ catch {
51
+ console.log(` • ${chalk.cyan(name)}`);
52
+ }
53
+ }
54
+ console.log('');
55
+ }
56
+ catch (e) {
57
+ spinner.fail(chalk.red(`KV 讀取失敗:${e instanceof Error ? e.message : e}`));
58
+ process.exit(1);
59
+ }
60
+ }
61
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdLogs(workflowName: string): Promise<void>;
2
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAQA,wBAAsB,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyEjE"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * acr logs <workflow_name> — 顯示最近執行記錄
3
+ */
4
+ import chalk from 'chalk';
5
+ import ora from 'ora';
6
+ import { loadConfig } from '../lib/config.js';
7
+ import { CfKvClient } from '../lib/cf-api.js';
8
+ export async function cmdLogs(workflowName) {
9
+ const config = loadConfig();
10
+ if (!config.cloudflare_account_id || !config.cf_api_token) {
11
+ console.error(chalk.red('缺少 Cloudflare 設定,請執行 acr init'));
12
+ process.exit(1);
13
+ }
14
+ const namespaceId = config.mode === 'standard'
15
+ ? config.user_kv_namespace_id
16
+ : config.webhooks_kv_namespace_id;
17
+ if (!namespaceId) {
18
+ console.error(chalk.red('缺少 KV Namespace ID,請執行 acr init'));
19
+ process.exit(1);
20
+ }
21
+ const kv = new CfKvClient({
22
+ accountId: config.cloudflare_account_id,
23
+ namespaceId,
24
+ apiToken: config.cf_api_token,
25
+ });
26
+ const spinner = ora(`讀取 "${workflowName}" 執行記錄`).start();
27
+ try {
28
+ const keys = await kv.list(`log:${workflowName}:`);
29
+ spinner.stop();
30
+ if (keys.length === 0) {
31
+ console.log(chalk.yellow(`\n "${workflowName}" 沒有執行記錄。\n`));
32
+ return;
33
+ }
34
+ // 依時間排序(key 格式:log:{name}:{timestamp})
35
+ const sorted = keys.sort((a, b) => b.name.localeCompare(a.name)).slice(0, 20);
36
+ console.log(chalk.bold(`\n "${workflowName}" 最近 ${sorted.length} 次執行記錄\n`));
37
+ for (const key of sorted) {
38
+ try {
39
+ const raw = await kv.get(key.name);
40
+ if (!raw)
41
+ continue;
42
+ const log = JSON.parse(raw);
43
+ const icon = log.success ? chalk.green('✓') : chalk.red('✗');
44
+ const date = new Date(log.executed_at).toLocaleString('zh-TW');
45
+ const duration = chalk.gray(`${log.duration_ms}ms`);
46
+ if (log.success) {
47
+ console.log(` ${icon} ${date} ${duration}`);
48
+ }
49
+ else {
50
+ console.log(` ${icon} ${date} ${duration} ${chalk.red(`失敗節點:${log.failed_node ?? '未知'}`)}`);
51
+ if (log.error) {
52
+ console.log(chalk.red(` ${log.error.slice(0, 100)}`));
53
+ }
54
+ }
55
+ }
56
+ catch {
57
+ // 跳過無法解析的記錄
58
+ }
59
+ }
60
+ console.log('');
61
+ }
62
+ catch (e) {
63
+ spinner.fail(chalk.red(`KV 讀取失敗:${e instanceof Error ? e.message : e}`));
64
+ process.exit(1);
65
+ }
66
+ }
67
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare function cmdParts(): Promise<void>;
2
+ export declare function cmdPartsScaffold(componentId: string): Promise<void>;
3
+ export declare function cmdPartsPublish(componentDir: string, options: {
4
+ status?: string;
5
+ }): Promise<void>;
6
+ //# sourceMappingURL=parts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/commands/parts.ts"],"names":[],"mappings":"AA0BA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAkE9C;AAED,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAwBzE;AAyBD,wBAAsB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEvG"}
@@ -0,0 +1,246 @@
1
+ /**
2
+ * acr parts — 列出所有可用零件(按類型分組,含統計與 author)
3
+ * acr parts scaffold <component> — 輸出 config 範本
4
+ * acr parts publish <component> — 提交零件至公眾 registry
5
+ */
6
+ import { readFileSync, existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import chalk from 'chalk';
9
+ import ora from 'ora';
10
+ import { loadConfig } from '../lib/config.js';
11
+ const REGISTRY_URL = 'https://registry.arcrun.dev';
12
+ export async function cmdParts() {
13
+ const spinner = ora('從 registry.arcrun.dev 取得零件清單').start();
14
+ let components = [];
15
+ try {
16
+ const res = await fetch(`${REGISTRY_URL}/components`);
17
+ if (res.ok) {
18
+ const data = await res.json();
19
+ components = data.components ?? [];
20
+ }
21
+ spinner.stop();
22
+ }
23
+ catch {
24
+ spinner.stop();
25
+ console.log(chalk.yellow(' 無法連線 registry.arcrun.dev,顯示本地零件清單\n'));
26
+ }
27
+ if (components.length === 0) {
28
+ // fallback:顯示本地 registry 目錄中的零件
29
+ components = loadLocalComponents();
30
+ }
31
+ // 依 category 分組
32
+ const grouped = {};
33
+ for (const comp of components) {
34
+ const cat = comp.category ?? 'other';
35
+ if (!grouped[cat])
36
+ grouped[cat] = [];
37
+ grouped[cat].push(comp);
38
+ }
39
+ const categoryLabels = {
40
+ api: '整合類(Integration)',
41
+ logic: '控制類(Control Flow)',
42
+ data: '資料類(Data)',
43
+ ai: 'AI 類',
44
+ other: '其他',
45
+ };
46
+ console.log(chalk.bold('\n arcrun 零件庫\n'));
47
+ for (const [cat, comps] of Object.entries(grouped)) {
48
+ console.log(chalk.bold.underline(` ${categoryLabels[cat] ?? cat}`));
49
+ for (const comp of comps) {
50
+ const isAuthorOnly = comp.visibility === 'author_only';
51
+ const tag = isAuthorOnly ? chalk.yellow(' [待審核]') : '';
52
+ let statsLine = '';
53
+ if (!isAuthorOnly && comp.total_runs !== undefined) {
54
+ const rate = ((comp.success_rate ?? 1) * 100).toFixed(1);
55
+ const runs = comp.total_runs.toLocaleString();
56
+ const ms = Math.round(comp.avg_duration_ms ?? 0);
57
+ statsLine = chalk.gray(` ★ ${rate}% 成功 | ${runs} 次執行 | 平均 ${ms}ms`);
58
+ }
59
+ const authorStr = comp.author ? chalk.gray(` by ${comp.author}`) : '';
60
+ const credStr = comp.credentials_required?.length
61
+ ? chalk.yellow(` 🔑 需要 ${comp.credentials_required.map(c => c.key).join(', ')}`)
62
+ : '';
63
+ console.log(` • ${chalk.cyan(comp.canonical_id.padEnd(20))}${comp.display_name}${tag}${authorStr}${credStr}`);
64
+ if (statsLine)
65
+ console.log(statsLine);
66
+ }
67
+ console.log('');
68
+ }
69
+ console.log(chalk.gray(' 使用 acr parts scaffold <component> 取得 config 範本'));
70
+ console.log(chalk.gray(' 使用 acr parts publish <component> 提交零件至公眾庫\n'));
71
+ }
72
+ export async function cmdPartsScaffold(componentId) {
73
+ // 優先從本地 registry 讀取 contract.yaml
74
+ const localContract = loadLocalContract(componentId);
75
+ if (!localContract) {
76
+ // 嘗試從 registry.arcrun.dev 取得
77
+ try {
78
+ const res = await fetch(`${REGISTRY_URL}/components/${componentId}/contract`);
79
+ if (!res.ok) {
80
+ console.error(chalk.red(`零件 "${componentId}" 不存在,執行 acr parts 查看可用清單`));
81
+ process.exit(1);
82
+ }
83
+ const data = await res.json();
84
+ printScaffold(componentId, data.config_example, data.credentials_required);
85
+ }
86
+ catch {
87
+ console.error(chalk.red(`無法取得 "${componentId}" 的 contract,請確認零件名稱`));
88
+ process.exit(1);
89
+ }
90
+ return;
91
+ }
92
+ const configExample = extractYamlField(localContract, 'config_example');
93
+ const credsRequired = extractCredentialsRequired(localContract);
94
+ printScaffold(componentId, configExample, credsRequired);
95
+ }
96
+ function printScaffold(componentId, configExample, credsRequired) {
97
+ console.log(chalk.bold(`\n ${componentId} — workflow.yaml config 範本\n`));
98
+ if (configExample) {
99
+ console.log(chalk.cyan(' # 貼入 workflow.yaml 的 config: 區塊'));
100
+ console.log(configExample.split('\n').map(l => ` ${l}`).join('\n'));
101
+ }
102
+ else {
103
+ console.log(chalk.yellow(' (無 config_example,請參考文檔)'));
104
+ }
105
+ if (credsRequired && credsRequired.length > 0) {
106
+ console.log(chalk.bold('\n credentials.yaml 範本(加入後執行 acr creds push)\n'));
107
+ for (const cred of credsRequired) {
108
+ console.log(chalk.cyan(` # ${cred.type}(${cred.inject_as} 欄位自動注入)`));
109
+ console.log(` ${cred.key}: "your-${cred.type}-token"\n`);
110
+ }
111
+ }
112
+ }
113
+ export async function cmdPartsPublish(componentDir, options) {
114
+ if (options.status) {
115
+ // 查詢審核進度
116
+ try {
117
+ const res = await fetch(`${REGISTRY_URL}/submit/status/${options.status}`);
118
+ const data = await res.json();
119
+ console.log(chalk.bold(`\n 提交狀態:${options.status}\n`));
120
+ console.log(` 狀態:${data.status}`);
121
+ if (data.visibility)
122
+ console.log(` Visibility:${data.visibility}`);
123
+ if (data.failed_step)
124
+ console.log(chalk.red(` 失敗步驟:${data.failed_step}`));
125
+ if (data.reason)
126
+ console.log(chalk.red(` 原因:${data.reason}`));
127
+ if (data.approved_at)
128
+ console.log(chalk.green(` 核准時間:${data.approved_at}`));
129
+ }
130
+ catch (e) {
131
+ console.error(chalk.red(`查詢失敗:${e instanceof Error ? e.message : e}`));
132
+ }
133
+ return;
134
+ }
135
+ const config = loadConfig();
136
+ if (!config.api_key) {
137
+ console.error(chalk.red('缺少 API Key,請執行 acr init'));
138
+ process.exit(1);
139
+ }
140
+ // 讀取零件目錄
141
+ const contractPath = join(componentDir, 'component.contract.yaml');
142
+ const mainGoPath = join(componentDir, 'main.go');
143
+ const wasmName = componentDir.split('/').pop() ?? componentDir;
144
+ const wasmPath = join(componentDir, `${wasmName}.wasm`);
145
+ if (!existsSync(contractPath)) {
146
+ console.error(chalk.red(`找不到 ${contractPath}`));
147
+ process.exit(1);
148
+ }
149
+ if (!existsSync(wasmPath)) {
150
+ console.error(chalk.red(`找不到 ${wasmPath}(請先編譯:tinygo build -o ${wasmName}.wasm -target wasi .)`));
151
+ process.exit(1);
152
+ }
153
+ const spinner = ora('提交零件至 registry.arcrun.dev').start();
154
+ const formData = new FormData();
155
+ formData.append('contract', new Blob([readFileSync(contractPath)], { type: 'application/yaml' }), 'component.contract.yaml');
156
+ if (existsSync(mainGoPath)) {
157
+ formData.append('source', new Blob([readFileSync(mainGoPath)], { type: 'text/plain' }), 'main.go');
158
+ }
159
+ formData.append('wasm', new Blob([readFileSync(wasmPath)], { type: 'application/wasm' }), `${wasmName}.wasm`);
160
+ try {
161
+ const res = await fetch(`${REGISTRY_URL}/submit`, {
162
+ method: 'POST',
163
+ headers: { 'X-Arcrun-API-Key': config.api_key },
164
+ body: formData,
165
+ });
166
+ if (!res.ok) {
167
+ const err = await res.text();
168
+ spinner.fail(chalk.red(`提交失敗(${res.status}):${err.slice(0, 200)}`));
169
+ process.exit(1);
170
+ }
171
+ const data = await res.json();
172
+ spinner.succeed(chalk.green(`✓ 提交成功`));
173
+ console.log(`\n Submission ID:${chalk.cyan(data.submission_id)}`);
174
+ console.log(` 狀態:${data.status}`);
175
+ if (data.visibility)
176
+ console.log(` Visibility:${data.visibility}`);
177
+ console.log(chalk.gray(`\n 查詢進度:acr parts publish --status ${data.submission_id}\n`));
178
+ }
179
+ catch (e) {
180
+ spinner.fail(chalk.red(`提交失敗:${e instanceof Error ? e.message : e}`));
181
+ process.exit(1);
182
+ }
183
+ }
184
+ // ── helpers ──────────────────────────────────────────────────────────────────
185
+ function loadLocalComponents() {
186
+ // 嘗試從相對路徑尋找 registry/components
187
+ const dirs = [
188
+ join(process.cwd(), 'registry/components'),
189
+ join(process.cwd(), '../registry/components'),
190
+ ];
191
+ for (const dir of dirs) {
192
+ if (existsSync(dir)) {
193
+ const components = [];
194
+ const { readdirSync } = require('node:fs');
195
+ for (const name of readdirSync(dir)) {
196
+ const contractPath = join(dir, name, 'component.contract.yaml');
197
+ if (existsSync(contractPath)) {
198
+ const raw = readFileSync(contractPath, 'utf8');
199
+ const canonical_id = extractYamlScalar(raw, 'canonical_id') ?? name;
200
+ const display_name = extractYamlScalar(raw, 'display_name') ?? name;
201
+ const category = extractYamlScalar(raw, 'category') ?? 'other';
202
+ const description = extractYamlScalar(raw, 'description') ?? '';
203
+ components.push({ canonical_id, display_name, category, description });
204
+ }
205
+ }
206
+ return components;
207
+ }
208
+ }
209
+ return [];
210
+ }
211
+ function loadLocalContract(componentId) {
212
+ const dirs = [
213
+ join(process.cwd(), `registry/components/${componentId}/component.contract.yaml`),
214
+ join(process.cwd(), `../registry/components/${componentId}/component.contract.yaml`),
215
+ ];
216
+ for (const p of dirs) {
217
+ if (existsSync(p))
218
+ return readFileSync(p, 'utf8');
219
+ }
220
+ return null;
221
+ }
222
+ function extractYamlScalar(yaml, key) {
223
+ const m = yaml.match(new RegExp(`^${key}:\\s*["']?([^"'\\n]+)["']?`, 'm'));
224
+ return m?.[1]?.trim();
225
+ }
226
+ function extractYamlField(yaml, field) {
227
+ const m = yaml.match(new RegExp(`^${field}:\\s*\\|\\n((?:[ \\t]+[^\\n]*\\n?)*)`, 'm'));
228
+ return m?.[1];
229
+ }
230
+ function extractCredentialsRequired(yaml) {
231
+ const section = yaml.match(/credentials_required:\s*([\s\S]*?)(?=\n\w|\n#|$)/);
232
+ if (!section)
233
+ return [];
234
+ const items = [];
235
+ const blocks = section[1].split(/\n - /).slice(1);
236
+ for (const block of blocks) {
237
+ const key = block.match(/key:\s*["']?([^"'\n]+)["']?/)?.[1]?.trim();
238
+ const type = block.match(/type:\s*["']?([^"'\n]+)["']?/)?.[1]?.trim();
239
+ const inject_as = block.match(/inject_as:\s*["']?([^"'\n]+)["']?/)?.[1]?.trim();
240
+ if (key && type && inject_as) {
241
+ items.push({ key, type, inject_as });
242
+ }
243
+ }
244
+ return items;
245
+ }
246
+ //# sourceMappingURL=parts.js.map