feat(harness): 用戶 CC harness(acr install-harness)+ 公開 repo 只留對外
讓「用 arcrun 開發」的用戶,他的 CC 自動載入 arcrun 防護、不退回自寫 Python。 新增 user-cc-harness(SDD: .agents/specs/user-cc-harness,本機): - acr install-harness:冪等裝進用戶當前專案(新/舊專案皆可),acr init 末尾也順便裝 - CLAUDE.md arcrun 區塊(標記包夾,append 不破壞既有) - .claude/skills/arcrun-mindset(世界觀 + 資源去哪取 acr parts/auth-recipe) - .claude/commands/arcrun.md(/arcrun slash command) - .claude/hooks/arcrun-guard.sh(python→提醒不硬擋、暴露→exit 2、每條含正路) - settings.json 合併 hook(不覆蓋用戶既有 hooks/設定) - llms.txt + README「給 AI」段:第一接觸點(用戶丟連結,CC 讀了知道第一步 install-harness) 含 CF 憑證白話照抄式引導(不對用戶講 KV/Worker/R2 術語) - harness 素材內嵌 npm 套件(cli/harness/,files 帶上),不依賴用戶有 arcrun repo - 實測:空目錄/冪等/既有檔合併皆通過,tsc exit 0,npm pack 含 harness 5 檔 公開 repo 清理(richblack:用戶要用不要開發 arcrun): - git rm --cached 移除開發痕跡 + 思考過程出公開 repo(本機保留供 richblack 開發): .claude/CLAUDE.md/AGENTS.md/.agents/docs/DECISIONS/BACKLOG/landing/.github - .gitignore 防回流;補 MIT LICENSE MCP(P7)納入 install-harness/update 的接點已設計,實作待 MCP 對齊(BACKLOG 另一條線)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
<!-- arcrun-harness:start -->
|
||||
## 這個專案用 arcrun 做自動化
|
||||
|
||||
當需要「串服務 / 排程 / 打 API / 資料處理自動化」時:
|
||||
|
||||
- **用 arcrun 工作流,不要自己寫 Python/Node 一次性腳本。** arcrun 工作流是純文字、可複用、跑在使用者自己的 Cloudflare。選 arcrun 就是不想要每次重刻的腳本。
|
||||
- **打外部 API → 寫 recipe**(`acr recipe push`),不要自刻 HTTP client。recipe = http_request + 固定設定的 YAML,不用部署。
|
||||
- **先查能力再動手**:`acr parts`(看可用零件)、`acr auth-recipe list`(看支援的認證服務)。
|
||||
- **不要自製零件**(WASM component)——零件由 arcrun 維護、走 GitHub PR。你能擴充的是 recipe + 工作流。
|
||||
- **需要 AI 判斷/轉換時,你自己做**,再呼叫工作流做確定性的下一步。不要在工作流裡放零件回頭呼叫 LLM。
|
||||
- **暴露資料要人類同意**:部署對外 webhook / push recipe 會讓東西可被外部呼叫 → 停下來讓使用者明示同意,不替他決定公開。
|
||||
- **誠實**:沒打通就誠實說(缺 credential 標「未驗收:缺 X」),不假裝成功;完成以 HTTP 2xx / trace 為證,不口頭宣布。
|
||||
|
||||
開始前讀 **arcrun-mindset** Skill(世界觀)。使用者技術細節交給你,CLI 操作你來做。
|
||||
<!-- arcrun-harness:end -->
|
||||
@@ -0,0 +1,29 @@
|
||||
# 用 arcrun 完成這個自動化需求
|
||||
|
||||
使用者想做一個自動化。你的任務:用 arcrun 做出來,全程不要讓使用者自己寫程式。
|
||||
|
||||
## 鐵則
|
||||
- **用 arcrun 工作流 / recipe,絕不自己寫 Python/Node 腳本。** 使用者選 arcrun 就是不想要一次性腳本。
|
||||
- 打外部 API → 寫 recipe(`acr recipe push`),不自刻 HTTP client。
|
||||
- 不自製零件(WASM)—— 零件由 arcrun 維護。你能用的是現有零件 + recipe + 工作流。
|
||||
- 需要 AI 判斷時你自己做,不要讓工作流回頭呼叫 LLM。
|
||||
|
||||
## 步驟
|
||||
1. 先讀 **arcrun-mindset** Skill(世界觀 + 資源去哪取)。
|
||||
2. 跑 `acr parts` 看零件、`acr auth-recipe list` 看支援的認證。**先查再動手。**
|
||||
3. 把使用者需求拆成工作流(哪些零件、什麼順序、什麼條件),寫成 `.yaml`。
|
||||
4. 需要 credential(API key / token)→ 用 `acr auth-recipe scaffold <service>` 看要哪些,
|
||||
明確告訴使用者去哪取得、怎麼 `acr creds push`。
|
||||
5. `acr validate` 通過後 `acr push` 部署,告訴使用者 webhook URL / 怎麼 `acr run`。
|
||||
6. 完成給客觀證據(HTTP 2xx / trace),不要只說「做好了」。
|
||||
|
||||
## 遇到要暴露資料(對外 webhook)
|
||||
停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。不要替他決定公開。
|
||||
|
||||
## 還沒設定好 arcrun?
|
||||
若 `acr` 指令不存在或還沒 `acr init`:先帶使用者完成前置設定
|
||||
(裝 CLI → 拿 Cloudflare 帳號的兩串憑證 → `acr init --self-hosted`)。
|
||||
拿 Cloudflare 憑證時用白話照抄式引導,不要對使用者講 KV / Worker / R2 等術語。
|
||||
|
||||
## 使用者的需求
|
||||
(使用者打在 /arcrun 後面的文字會接在這裡)
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# arcrun-guard.sh — 用戶專案的 arcrun PreToolUse guard(由 acr install-harness 裝進 .claude/hooks/)
|
||||
#
|
||||
# 對象:在「用 arcrun 開發」的專案裡工作的 CC。擋它走歪(退回自寫 Python / 不用 recipe / 未經同意暴露)。
|
||||
# 與 arcrun repo 開發版 hook 完全不同(那個擋的是開發 arcrun 本身)。
|
||||
#
|
||||
# 鐵則(user-cc-harness design §0.5):每次擋下/提醒都要給「具體怎麼做才對」的正路,不只說「不行」。
|
||||
#
|
||||
# 退出碼:0=允許(可附 stderr 提醒);2=硬擋(stderr 回給 CC)。
|
||||
# 分級(design §4):多數用「提醒不硬擋」(避免誤殺正常 python);硬擋只留給「未經同意暴露資料」。
|
||||
|
||||
set -o pipefail
|
||||
INPUT=$(cat)
|
||||
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
||||
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
|
||||
|
||||
remind() {
|
||||
# 提醒但放行(exit 0)。CC 看到 stderr,自己判斷是否真要繼續。
|
||||
echo "💡 arcrun 提醒:$1" >&2
|
||||
echo " 正路:$2" >&2
|
||||
exit 0
|
||||
}
|
||||
block() {
|
||||
echo "❌ arcrun guard 擋下:$1" >&2
|
||||
echo " 正路:$2" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
# ── 硬擋:未經人類同意的暴露動作(明確越界,mindset §5)──────────────
|
||||
# 非互動環境下 CC 自己跑「部署對外 webhook / push recipe」= 替人類決定公開。
|
||||
if echo "$CMD" | grep -qE "acr (push|recipe push)\b"; then
|
||||
if [ ! -t 0 ] && [ "${ARCRUN_HUMAN_CONFIRMED:-}" != "1" ]; then
|
||||
block "在非互動環境自動執行暴露動作(acr push / recipe push 會讓東西可被外部呼叫)" \
|
||||
"把這動作交給人類在終端機執行,或先讓使用者明示同意。不要替使用者決定公開。"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── 提醒(不硬擋):退回自寫 Python/Node 一次性自動化 ──────────────────
|
||||
# 「我先用 Python 測試」這類退回熟悉工具的傾向。python 不絕對錯(可能跑測試),故提醒不擋。
|
||||
if echo "$CMD" | grep -qE "(^|[;&| ])(python3?|node)[ ]+[^ ]+\.(py|js|mjs|ts)\b"; then
|
||||
# 排除明顯的測試 / 既有工具呼叫(pytest / npm test / jest 等)降低誤判
|
||||
if ! echo "$CMD" | grep -qE "(pytest|jest|vitest|npm (run )?test|mocha|\btest_)"; then
|
||||
remind "偵測到用 python/node 跑腳本。這專案用 arcrun,串服務/自動化不要自刻一次性腳本。" \
|
||||
"先跑 \`acr parts\` 看有哪些零件,把需求寫成 workflow.yaml 用 \`acr run\`。若這確實不是自動化(例如跑測試/別的工具),忽略本提醒。"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── 提醒(不硬擋):自寫打固定 API 的 script,而非 recipe ──────────────
|
||||
if echo "$CMD" | grep -qE "(curl|fetch|requests\.(get|post)|axios).*https?://"; then
|
||||
remind "偵測到自己打外部 API。arcrun 裡「打固定 endpoint」應寫成 recipe,不自刻 HTTP 呼叫。" \
|
||||
"用 \`acr recipe push\` 把這個 API 包成 recipe,workflow 裡用 component 引用它。見 arcrun-mindset Skill。"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": ".claude/hooks/arcrun-guard.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
name: arcrun-mindset
|
||||
description: >-
|
||||
arcrun 的世界觀 — 用 arcrun 開發自動化時的預設心態 + 資源去哪取。當你(AI 操盤手)要在
|
||||
arcrun 上做任何事(串服務、處理資料、認證、把東西開放給人用)前讀這個。它讓你做出「方向對」
|
||||
的選擇、知道資源在哪,避免技術上能跑但架構上錯、或自己重刻 arcrun 已有的東西。
|
||||
---
|
||||
|
||||
# arcrun mindset(給 AI 操盤手)
|
||||
|
||||
你在 arcrun 上幫使用者開發自動化。arcrun 很簡單,簡單到你常會把它想複雜、或退回自己熟悉的
|
||||
Python/Node 自刻。這份幫你在岔路上選對方向,並告訴你資源在哪。
|
||||
|
||||
---
|
||||
|
||||
## 0. 一句話世界觀
|
||||
|
||||
**arcrun 裡幾乎所有東西都是工作流(workflow)。** 工作流 = 一張紙,寫「用哪些零件、什麼順序、什麼條件」。
|
||||
你大部分時間在寫紙、改紙,不是在造新零件、也不是自己寫腳本。
|
||||
|
||||
---
|
||||
|
||||
## 1. 工作流是 default,不要退回自己寫 Python
|
||||
|
||||
使用者選 arcrun,就是不要「每次重刻、跑完即丟」的腳本。所以你的預設順序:
|
||||
|
||||
1. **先想能不能用工作流做**(串現有零件 / recipe + 流程控制)。99% 可以。
|
||||
2. 要打的服務有 HTTP API、但沒有對應 recipe → **寫一個 recipe**(http_request + 固定設定 YAML,不用部署、不用審核)。
|
||||
3. **只有**封閉純邏輯(流程控制 / 資料處理)、現有零件不夠、且值得全 arcrun 重用 → 才考慮零件(而零件走 PR,不是你現在做)。
|
||||
|
||||
> 典型走歪:「我先用 Python 測一下」。停。使用者要的是 arcrun 工作流。先 `acr parts` 看有什麼,用工作流串。
|
||||
|
||||
## 2. 資源去哪取(不要自己重造 arcrun 已有的)
|
||||
|
||||
| 你想知道 | 跑這個 |
|
||||
|---|---|
|
||||
| 有哪些零件可用 | `acr parts` |
|
||||
| 某零件的設定範本 | `acr parts scaffold <name>` |
|
||||
| 支援哪些服務的認證 | `acr auth-recipe list` |
|
||||
| 某服務認證要哪些 credential + 範例 | `acr auth-recipe scaffold <service>` |
|
||||
| 已上傳的 recipe | `acr recipe list` |
|
||||
| 工作流語法、指令 | `acr --help` |
|
||||
|
||||
**先查再動手**——arcrun 多半已經有你要的零件 / recipe / 認證,不要自刻。
|
||||
|
||||
## 3. arcrun 是你(AI)用的工具,不是工具回頭呼叫 AI
|
||||
|
||||
需要智慧判斷 / 自然語言轉換時,**你自己做**,再呼叫工作流執行確定性的下一步。
|
||||
**不要在工作流中間放零件回頭呼叫 LLM**。arcrun 的大腦就是操盤的你。
|
||||
|
||||
## 4. arcrun 不替你做授權判斷
|
||||
|
||||
API 打不打得通由發 key 的服務決定。401/403 是對方服務在行使授權,**不是 arcrun 的 bug、不是你做錯**。
|
||||
不要在 arcrun 裡建「允許/禁止某 endpoint」的二次授權清單。
|
||||
|
||||
## 5. 把東西開放給別人用 = 要使用者明示同意
|
||||
|
||||
部署對外 webhook、push recipe 會讓資料/能力**可被外部呼叫**(暴露面):
|
||||
- 停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。**不替他決定公開。**
|
||||
- 非互動環境(你直跑)遇到 → 停,要人類確認,絕不自己塞 confirm 假裝同意。
|
||||
- arcrun 可提供保護(要求呼叫者帶 key / 限流)——提醒使用者。
|
||||
|
||||
## 6. 誠實(最重要)
|
||||
|
||||
- **不假綠**:沒打通就誠實說。缺 credential 打不到 2xx → 標「未驗收:缺 X」,不 mock 充綠燈。
|
||||
- **不假裝防偽 / 不代替人類確認**有風險的動作(暴露資料)。
|
||||
- **完成 = 客觀證據**(HTTP 2xx + trace),不是口頭「做好了」。
|
||||
|
||||
---
|
||||
|
||||
## 怎麼用這份 mindset
|
||||
|
||||
每次準備動手,先過一遍:
|
||||
1. 這能用工作流 / recipe 做嗎?(多半能 → 別自己寫 Python、別造零件)
|
||||
2. 我查過 `acr parts` / `acr auth-recipe` 了嗎?(arcrun 可能已有)
|
||||
3. 我是不是讓工作流回頭呼叫 AI?(是 → 改成我自己做)
|
||||
4. 這動作會把資料開放給別人嗎?(會 → 要使用者明示同意)
|
||||
5. 我有沒有假裝(假綠 / 假防偽 / 代替人類確認)?(有 → 停,誠實標明)
|
||||
+5
-4
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "arcrun",
|
||||
"version": "1.1.0",
|
||||
"description": "AI Workflow CLI for arcrun — deploy and run WASM-based AI workflows on Cloudflare",
|
||||
"version": "1.2.0",
|
||||
"description": "AI Workflow CLI for arcrun — self-host WASM-based AI workflows on your own Cloudflare",
|
||||
"bin": {
|
||||
"acr": "dist/index.js"
|
||||
},
|
||||
@@ -27,7 +27,8 @@
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
"dist/",
|
||||
"harness/"
|
||||
],
|
||||
"keywords": [
|
||||
"cloudflare",
|
||||
@@ -40,6 +41,6 @@
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/richblack/arcrun.git"
|
||||
"url": "git+https://github.com/uncle6me-web/Arcrun.git"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
type DeployContext,
|
||||
} from '../lib/deploy.js';
|
||||
import { API_RECIPE_SEEDS } from '../lib/api-recipe-seeds.js';
|
||||
import { cmdInstallHarness } from './install-harness.js';
|
||||
|
||||
const ARCRUN_REGISTER_URL = 'https://cypher.arcrun.dev/register';
|
||||
|
||||
@@ -42,6 +43,14 @@ export async function cmdInit(options: { local?: boolean; selfHosted?: boolean }
|
||||
} finally {
|
||||
rl.close();
|
||||
}
|
||||
|
||||
// init 末尾順便裝 CC harness 進當前專案(SDD user-cc-harness §2:init 裝 + 可單獨裝)。
|
||||
// 失敗不影響 init 本身(harness 是加分,可事後 acr install-harness 補)。
|
||||
try {
|
||||
await cmdInstallHarness();
|
||||
} catch (e) {
|
||||
console.log(chalk.gray(` (harness 安裝略過:${e instanceof Error ? e.message : e};可稍後跑 acr install-harness)`));
|
||||
}
|
||||
}
|
||||
|
||||
async function initLocal(): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* acr install-harness — 把「用戶 CC harness」裝進當前專案。
|
||||
*
|
||||
* 對象:在「用 arcrun 開發」的專案裡工作的 CC + 使用者。讓使用者的 CC 自動載入 arcrun 防護:
|
||||
* - CLAUDE.md 區塊(事前提醒:別自寫 Python)
|
||||
* - .claude/skills/arcrun-mindset/(世界觀 + 資源去哪取)
|
||||
* - .claude/commands/arcrun.md(/arcrun slash command)
|
||||
* - .claude/hooks/arcrun-guard.sh + settings.json(做錯被糾正)
|
||||
*
|
||||
* 冪等:重裝不重複、不破壞使用者既有 CLAUDE.md / settings。
|
||||
* SDD:.agents/specs/user-cc-harness/design.md §2
|
||||
*/
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, chmodSync, readdirSync,
|
||||
} from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import chalk from 'chalk';
|
||||
|
||||
/** harness 素材根目錄(內嵌 npm 套件,SSOT=cli/harness/)。
|
||||
* build 後此檔在 dist/commands/,harness/ 在套件根 → ../../harness。 */
|
||||
function harnessRoot(): string {
|
||||
const here = dirname(fileURLToPath(import.meta.url)); // .../dist/commands
|
||||
return join(here, '..', '..', 'harness'); // .../harness
|
||||
}
|
||||
|
||||
const START = '<!-- arcrun-harness:start -->';
|
||||
const END = '<!-- arcrun-harness:end -->';
|
||||
|
||||
export async function cmdInstallHarness(): Promise<void> {
|
||||
const cwd = process.cwd();
|
||||
const src = harnessRoot();
|
||||
|
||||
if (!existsSync(src)) {
|
||||
console.error(chalk.red(`找不到 harness 素材(${src})。套件安裝可能不完整,請重裝 arcrun。`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.bold('\n 安裝 arcrun harness 到當前專案\n'));
|
||||
|
||||
// 1. CLAUDE.md:append/取代 arcrun 區塊(標記包夾,冪等)
|
||||
installClaudeBlock(cwd, src);
|
||||
// 2. mindset Skill
|
||||
copyTree(join(src, 'skills'), join(cwd, '.claude', 'skills'));
|
||||
console.log(chalk.green(' ✓ .claude/skills/arcrun-mindset/'));
|
||||
// 3. /arcrun command
|
||||
copyTree(join(src, 'commands'), join(cwd, '.claude', 'commands'));
|
||||
console.log(chalk.green(' ✓ .claude/commands/arcrun.md'));
|
||||
// 4. hook
|
||||
const hookDst = join(cwd, '.claude', 'hooks', 'arcrun-guard.sh');
|
||||
mkdirSync(dirname(hookDst), { recursive: true });
|
||||
copyFileSync(join(src, 'hooks', 'arcrun-guard.sh'), hookDst);
|
||||
chmodSync(hookDst, 0o755);
|
||||
console.log(chalk.green(' ✓ .claude/hooks/arcrun-guard.sh'));
|
||||
// 5. settings.json:合併 hook 註冊(不覆蓋使用者既有設定)
|
||||
mergeSettings(cwd, src);
|
||||
console.log(chalk.green(' ✓ .claude/settings.json(已合併 arcrun guard hook)'));
|
||||
|
||||
console.log(chalk.gray('\n 提示:'));
|
||||
console.log(chalk.gray(' • 首次在此專案開 Claude Code 會要求「信任工作區」,按信任 hook 才生效。'));
|
||||
console.log(chalk.gray(' • 之後跟 CC 說需求即可(或打 /arcrun <你的需求>)。'));
|
||||
console.log(chalk.gray(' • CC 偏好 MCP?可另跑 acr update 連 arcrun MCP(MCP 對齊中,optional)。\n'));
|
||||
}
|
||||
|
||||
/** CLAUDE.md:無→建;有 arcrun 區塊→取代;有但無區塊→append。標記包夾,冪等。 */
|
||||
function installClaudeBlock(cwd: string, src: string): void {
|
||||
const block = readFileSync(join(src, 'CLAUDE.block.md'), 'utf8').trim();
|
||||
const path = join(cwd, 'CLAUDE.md');
|
||||
|
||||
if (!existsSync(path)) {
|
||||
writeFileSync(path, block + '\n', 'utf8');
|
||||
console.log(chalk.green(' ✓ CLAUDE.md(已建立,含 arcrun 區塊)'));
|
||||
return;
|
||||
}
|
||||
const cur = readFileSync(path, 'utf8');
|
||||
if (cur.includes(START) && cur.includes(END)) {
|
||||
// 取代既有區塊
|
||||
const re = new RegExp(escapeRe(START) + '[\\s\\S]*?' + escapeRe(END));
|
||||
writeFileSync(path, cur.replace(re, block), 'utf8');
|
||||
console.log(chalk.green(' ✓ CLAUDE.md(已更新 arcrun 區塊)'));
|
||||
} else {
|
||||
writeFileSync(path, cur.replace(/\s*$/, '') + '\n\n' + block + '\n', 'utf8');
|
||||
console.log(chalk.green(' ✓ CLAUDE.md(已附加 arcrun 區塊,未動既有內容)'));
|
||||
}
|
||||
}
|
||||
|
||||
/** 把 settings.fragment.json 的 hook 合併進專案 settings.json(不覆蓋使用者既有 hooks/設定)。 */
|
||||
function mergeSettings(cwd: string, src: string): void {
|
||||
const fragment = JSON.parse(readFileSync(join(src, 'settings.fragment.json'), 'utf8'));
|
||||
const path = join(cwd, '.claude', 'settings.json');
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
|
||||
let settings: Record<string, unknown> = {};
|
||||
if (existsSync(path)) {
|
||||
try { settings = JSON.parse(readFileSync(path, 'utf8')); } catch { settings = {}; }
|
||||
}
|
||||
|
||||
const hooks = (settings.hooks ?? {}) as Record<string, unknown[]>;
|
||||
const fragHooks = fragment.hooks as Record<string, unknown[]>;
|
||||
for (const [event, entries] of Object.entries(fragHooks)) {
|
||||
const existing = Array.isArray(hooks[event]) ? hooks[event] : [];
|
||||
// 去重:避免重裝重複加 arcrun-guard
|
||||
const serialized = new Set(existing.map(e => JSON.stringify(e)));
|
||||
for (const e of entries) {
|
||||
if (!serialized.has(JSON.stringify(e))) existing.push(e);
|
||||
}
|
||||
hooks[event] = existing;
|
||||
}
|
||||
settings.hooks = hooks;
|
||||
writeFileSync(path, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
/** 遞迴複製目錄樹(覆蓋同名檔)。 */
|
||||
function copyTree(srcDir: string, dstDir: string): void {
|
||||
if (!existsSync(srcDir)) return;
|
||||
mkdirSync(dstDir, { recursive: true });
|
||||
for (const name of readdirSync(srcDir, { withFileTypes: true })) {
|
||||
const s = join(srcDir, name.name);
|
||||
const d = join(dstDir, name.name);
|
||||
if (name.isDirectory()) copyTree(s, d);
|
||||
else copyFileSync(s, d);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeRe(s: string): string {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { cmdRecipePush, cmdRecipeList, cmdRecipeDelete } from './commands/recipe
|
||||
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 { cmdAuthRecipeList, cmdAuthRecipeInfo, cmdAuthRecipeScaffold } from './commands/auth-recipe.js';
|
||||
|
||||
const program = new Command();
|
||||
@@ -127,4 +128,10 @@ program
|
||||
.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());
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
Reference in New Issue
Block a user