arcrun 1.1.0 → 1.3.1
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.
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +39 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/creds.d.ts.map +1 -1
- package/dist/commands/creds.js +21 -3
- package/dist/commands/creds.js.map +1 -1
- package/dist/commands/init.d.ts +5 -2
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +154 -19
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install-harness.d.ts +2 -0
- package/dist/commands/install-harness.d.ts.map +1 -0
- package/dist/commands/install-harness.js +122 -0
- package/dist/commands/install-harness.js.map +1 -0
- package/dist/commands/mcp-setup.d.ts +12 -0
- package/dist/commands/mcp-setup.d.ts.map +1 -0
- package/dist/commands/mcp-setup.js +42 -0
- package/dist/commands/mcp-setup.js.map +1 -0
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +42 -11
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/recipe.d.ts.map +1 -1
- package/dist/commands/recipe.js +63 -1
- package/dist/commands/recipe.js.map +1 -1
- package/dist/commands/update.d.ts +13 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +89 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/validate.js +2 -2
- package/dist/commands/validate.js.map +1 -1
- package/dist/index.js +43 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/api-recipe-seeds.d.ts +32 -0
- package/dist/lib/api-recipe-seeds.d.ts.map +1 -0
- package/dist/lib/api-recipe-seeds.js +108 -0
- package/dist/lib/api-recipe-seeds.js.map +1 -0
- package/dist/lib/cf-api.d.ts +22 -0
- package/dist/lib/cf-api.d.ts.map +1 -1
- package/dist/lib/cf-api.js +58 -0
- package/dist/lib/cf-api.js.map +1 -1
- package/dist/lib/config.d.ts +34 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +176 -8
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/deploy.d.ts +56 -0
- package/dist/lib/deploy.d.ts.map +1 -0
- package/dist/lib/deploy.js +239 -0
- package/dist/lib/deploy.js.map +1 -0
- package/dist/lib/exposure-warning.d.ts +25 -0
- package/dist/lib/exposure-warning.d.ts.map +1 -0
- package/dist/lib/exposure-warning.js +92 -0
- package/dist/lib/exposure-warning.js.map +1 -0
- package/dist/lib/yaml-parser.d.ts.map +1 -1
- package/dist/lib/yaml-parser.js +7 -1
- package/dist/lib/yaml-parser.js.map +1 -1
- package/harness/CLAUDE.block.md +15 -0
- package/harness/commands/arcrun.md +29 -0
- package/harness/hooks/arcrun-guard.sh +54 -0
- package/harness/settings.fragment.json +16 -0
- package/harness/skills/arcrun-mindset/SKILL.md +78 -0
- package/package.json +5 -4
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* deploy.ts — self-hosted Worker 部署(installer 的「下載 repo tarball + wrangler deploy」段)
|
|
3
|
+
*
|
|
4
|
+
* 對應 SDD:.agents/specs/arcrun/sdk-and-website/self-hosted-init.md §6(commit wasm + codeload)
|
|
5
|
+
*
|
|
6
|
+
* 策略(richblack 2026-06-02):repo 自帶預編譯 wasm(.component-builds 下各 component.wasm,
|
|
7
|
+
* 見 rule 05 慣例變更)→ CLI 從 GitHub codeload tarball 拿完整部署物 → 注入用戶的 KV id
|
|
8
|
+
* → 用用戶自己的 CF token wrangler deploy。用戶不需 git / tinygo,只需 wrangler。
|
|
9
|
+
*/
|
|
10
|
+
import { execFileSync } from 'node:child_process';
|
|
11
|
+
import { mkdtempSync, readFileSync, writeFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
/** GitHub repo(codeload tarball 來源)。fork 者改這裡或用 ARCRUN_REPO env。
|
|
15
|
+
* 注意:repo 名大小寫敏感(codeload 路徑需完全一致)。*/
|
|
16
|
+
const ARCRUN_REPO = process.env.ARCRUN_REPO ?? 'uncle6me-web/Arcrun';
|
|
17
|
+
/**
|
|
18
|
+
* init 要建立的 KV namespace(title)。
|
|
19
|
+
* 前 7 個權威來源:.claude/rules/01-tech-stack.md 資料儲存表(cypher-executor 用)。
|
|
20
|
+
* SUBMISSIONS_KV:registry worker 用(component 投稿)。漏建會讓 registry deploy 失敗 →
|
|
21
|
+
* 壓測 §2.6/#11「20/21」根因(registry/wrangler.toml 綁 SUBMISSIONS_KV,但注入清單沒有它,
|
|
22
|
+
* 殘留官方舊 id → wrangler deploy 因 KV 不存在而失敗)。補進來後回到 21/21。
|
|
23
|
+
*/
|
|
24
|
+
export const REQUIRED_KV_NAMESPACES = [
|
|
25
|
+
'WEBHOOKS',
|
|
26
|
+
'CREDENTIALS_KV',
|
|
27
|
+
'RECIPES',
|
|
28
|
+
'USERS_KV',
|
|
29
|
+
'SESSIONS_KV',
|
|
30
|
+
'ANALYTICS_KV',
|
|
31
|
+
'EXEC_CONTEXT',
|
|
32
|
+
'SUBMISSIONS_KV',
|
|
33
|
+
];
|
|
34
|
+
/** 部署後要提示用戶手動 `wrangler secret put ENCRYPTION_KEY` 的 Worker。*/
|
|
35
|
+
export const SECRET_TARGET_WORKERS = [
|
|
36
|
+
'arcrun-cypher-executor',
|
|
37
|
+
'arcrun-auth-static-key',
|
|
38
|
+
'arcrun-auth-service-account',
|
|
39
|
+
];
|
|
40
|
+
/** 偵測 wrangler 是否已安裝(用戶前置:裝 CF CLI)。*/
|
|
41
|
+
export function wranglerAvailable() {
|
|
42
|
+
try {
|
|
43
|
+
execFileSync('wrangler', ['--version'], { stdio: 'ignore' });
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 下載 repo codeload tarball(含預編譯 wasm)→ 注入用戶 KV id → wrangler deploy 全部 Worker。
|
|
52
|
+
*
|
|
53
|
+
* SDD self-hosted-init.md §6.4:
|
|
54
|
+
* 1. 下載 codeload tarball(ref 預設 main)→ 解壓到暫存目錄
|
|
55
|
+
* 2. 各 wrangler.toml 注入 ctx.kvNamespaceIds + cypher-executor WORKER_SUBDOMAIN
|
|
56
|
+
* 3. tier1=.component-builds/* 先 → tier2=cypher-executor/registry 後,逐一 wrangler deploy
|
|
57
|
+
* 4. 回 cypherExecutorUrl = https://arcrun-cypher-executor.<subdomain>.workers.dev
|
|
58
|
+
*
|
|
59
|
+
* 誠實(mindset §7):任一 worker deploy 失敗會收集進 message 回報,不假裝全綠。
|
|
60
|
+
*
|
|
61
|
+
* @param ctx 部署上下文
|
|
62
|
+
* @param ref git ref(branch / tag),預設 main;acr update 可帶 tag
|
|
63
|
+
*/
|
|
64
|
+
export async function downloadAndDeploy(ctx, ref = 'main') {
|
|
65
|
+
// 1. 下載 + 解壓 codeload tarball
|
|
66
|
+
let root;
|
|
67
|
+
try {
|
|
68
|
+
root = await downloadRepoTarball(ref);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return {
|
|
72
|
+
implemented: true,
|
|
73
|
+
message: `下載部署物失敗(${e instanceof Error ? e.message : e})。確認網路 + ARCRUN_REPO=${ARCRUN_REPO} 可達。`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// 2. 列出要部署的 worker 目錄(含 wrangler.toml),分 tier
|
|
77
|
+
const { tier1, tier2 } = discoverWorkerDirs(root);
|
|
78
|
+
if (tier1.length === 0 && tier2.length === 0) {
|
|
79
|
+
return { implemented: true, message: `部署物中找不到任何 wrangler.toml(root=${root})。` };
|
|
80
|
+
}
|
|
81
|
+
// 3. 對每個 worker:注入 KV id(+ cypher WORKER_SUBDOMAIN)→ wrangler deploy。tier1 先 tier2 後。
|
|
82
|
+
const failures = [];
|
|
83
|
+
let deployed = 0;
|
|
84
|
+
for (const dir of [...tier1, ...tier2]) {
|
|
85
|
+
const tomlPath = join(dir, 'wrangler.toml');
|
|
86
|
+
try {
|
|
87
|
+
injectWranglerConfig(tomlPath, ctx);
|
|
88
|
+
runWranglerDeploy(dir, ctx);
|
|
89
|
+
deployed++;
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
failures.push(`${dir}: ${e instanceof Error ? e.message : String(e)}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const cypherExecutorUrl = ctx.workerSubdomain
|
|
96
|
+
? `https://arcrun-cypher-executor.${ctx.workerSubdomain}.workers.dev`
|
|
97
|
+
: undefined;
|
|
98
|
+
if (failures.length > 0) {
|
|
99
|
+
return {
|
|
100
|
+
implemented: true,
|
|
101
|
+
cypherExecutorUrl,
|
|
102
|
+
message: `部署 ${deployed}/${tier1.length + tier2.length} 成功,${failures.length} 失敗(誠實回報,未假綠):\n` +
|
|
103
|
+
failures.map(f => ` ✗ ${f}`).join('\n'),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
implemented: true,
|
|
108
|
+
cypherExecutorUrl,
|
|
109
|
+
message: `部署完成:${deployed} 個 Worker 全部成功。`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** 下載 codeload tarball 解壓到暫存目錄,回傳解壓出的 repo root 路徑。*/
|
|
113
|
+
async function downloadRepoTarball(ref) {
|
|
114
|
+
const url = `https://codeload.github.com/${ARCRUN_REPO}/tar.gz/${ref}`;
|
|
115
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(120_000) });
|
|
116
|
+
if (!res.ok)
|
|
117
|
+
throw new Error(`codeload HTTP ${res.status}(${url})`);
|
|
118
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
119
|
+
const dir = mkdtempSync(join(tmpdir(), 'arcrun-deploy-'));
|
|
120
|
+
const tarPath = join(dir, 'repo.tar.gz');
|
|
121
|
+
writeFileSync(tarPath, buf);
|
|
122
|
+
// 用系統 tar 解壓(macOS/Linux 內建)。tarball 解出單一頂層目錄 {repo}-{ref}/。
|
|
123
|
+
execFileSync('tar', ['-xzf', tarPath, '-C', dir], { stdio: 'ignore' });
|
|
124
|
+
const entries = readdirSync(dir).filter(n => n !== 'repo.tar.gz');
|
|
125
|
+
const top = entries.find(n => statSync(join(dir, n)).isDirectory());
|
|
126
|
+
if (!top)
|
|
127
|
+
throw new Error('tarball 解壓後找不到頂層目錄');
|
|
128
|
+
return join(dir, top);
|
|
129
|
+
}
|
|
130
|
+
/** 掃解壓出的部署物,回傳 tier1(.component-builds/*)與 tier2(cypher-executor/registry)目錄清單。*/
|
|
131
|
+
function discoverWorkerDirs(root) {
|
|
132
|
+
const tier1 = [];
|
|
133
|
+
const tier2 = [];
|
|
134
|
+
const cbRoot = join(root, '.component-builds');
|
|
135
|
+
if (existsSync(cbRoot)) {
|
|
136
|
+
for (const name of readdirSync(cbRoot)) {
|
|
137
|
+
const dir = join(cbRoot, name);
|
|
138
|
+
// 需同時有 wrangler.toml 且有 component.wasm 才部署。
|
|
139
|
+
// 「錯做成零件」的(claude_api / km_writer / kbdb_upsert_block)wasm 沒 commit 進 repo
|
|
140
|
+
// (.gitignore 排除,待降級成工作流/recipe)→ codeload 拿到的目錄缺 wasm → 自然跳過,
|
|
141
|
+
// 不讓 wrangler deploy 因缺檔失敗。
|
|
142
|
+
if (existsSync(join(dir, 'wrangler.toml')) && existsSync(join(dir, 'component.wasm'))) {
|
|
143
|
+
tier1.push(dir);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const name of ['cypher-executor', 'registry']) {
|
|
148
|
+
const dir = join(root, name);
|
|
149
|
+
if (existsSync(join(dir, 'wrangler.toml')))
|
|
150
|
+
tier2.push(dir);
|
|
151
|
+
}
|
|
152
|
+
return { tier1, tier2 };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 注入用戶的 KV namespace id(取代 wrangler.toml 中各 binding 的 id)+ cypher WORKER_SUBDOMAIN,
|
|
156
|
+
* 並 strip 掉只有 arcrun 官方帳號才有的綁定(self-hosted fork 帳號沒有)。
|
|
157
|
+
*
|
|
158
|
+
* 為何 strip 而非刪 repo 內 toml(壓測 2026-06-04 阻斷項 #1#2#3#4):
|
|
159
|
+
* - repo 內各 worker toml 的 `[[routes]] zone_name="arcrun.dev"` 是**官方 prod CI 部署**需要的
|
|
160
|
+
* (對外開放零件)。直接從 repo 刪會破壞官方部署。
|
|
161
|
+
* - 但 fork 用戶**沒有 arcrun.dev zone** → wrangler deploy 找不到 zone 而失敗。
|
|
162
|
+
* - deploy.ts 只在 self-hosted 路徑跑,且改的是「暫存目錄副本」(SDD self-hosted-init.md §3 step 4),
|
|
163
|
+
* 不碰用戶 repo。所以在注入時 strip 掉這些官方專屬綁定 = 對的層級。
|
|
164
|
+
* - 每個 worker toml 都有 `workers_dev = true` → strip routes 後純靠 workers.dev URL,自架可達。
|
|
165
|
+
* - R2(`[[r2_buckets]]`)是 dead storage(registry-canon Phase 1.5),且綁卡違背開源免費 → 一併移除。
|
|
166
|
+
*/
|
|
167
|
+
function injectWranglerConfig(tomlPath, ctx) {
|
|
168
|
+
if (!existsSync(tomlPath))
|
|
169
|
+
return;
|
|
170
|
+
let toml = readFileSync(tomlPath, 'utf8');
|
|
171
|
+
// 對每個已建立的 KV namespace:把對應 binding 的 id 換成用戶的。
|
|
172
|
+
// 匹配 `[[kv_namespaces]] ... binding = "NAME" ... id = "OLD"` 的 id 行。
|
|
173
|
+
for (const [binding, id] of Object.entries(ctx.kvNamespaceIds)) {
|
|
174
|
+
if (!id)
|
|
175
|
+
continue;
|
|
176
|
+
const re = new RegExp(`(binding\\s*=\\s*"${binding}"\\s*\\n\\s*id\\s*=\\s*")[^"]*(")`, 'g');
|
|
177
|
+
toml = toml.replace(re, `$1${id}$2`);
|
|
178
|
+
}
|
|
179
|
+
// cypher-executor 的 WORKER_SUBDOMAIN(vars)換成用戶帳號 subdomain
|
|
180
|
+
if (ctx.workerSubdomain && /WORKER_SUBDOMAIN/.test(toml)) {
|
|
181
|
+
toml = toml.replace(/(WORKER_SUBDOMAIN\s*=\s*")[^"]*(")/, `$1${ctx.workerSubdomain}$2`);
|
|
182
|
+
}
|
|
183
|
+
toml = stripOfficialOnlyBindings(toml);
|
|
184
|
+
writeFileSync(tomlPath, toml, 'utf8');
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 移除 self-hosted fork 帳號沒有、會導致 wrangler deploy 失敗的官方專屬 TOML 區塊:
|
|
188
|
+
* - `[[routes]]`(含 pattern/zone_name):fork 沒有 arcrun.dev zone
|
|
189
|
+
* - `[[r2_buckets]]`:dead storage + 綁卡違背開源免費(registry-canon 1.5)
|
|
190
|
+
* - `[ai]`(Workers AI binding):免費帳號未必啟用,且自架預設不需要
|
|
191
|
+
* 純文字行級移除(TOML table 以空行 / 下一個 `[` 區塊結束)。worker 仍靠 `workers_dev = true` 對外。
|
|
192
|
+
*/
|
|
193
|
+
export function stripOfficialOnlyBindings(toml) {
|
|
194
|
+
const lines = toml.split('\n');
|
|
195
|
+
const out = [];
|
|
196
|
+
let skipping = false;
|
|
197
|
+
const isBlockHeader = (l) => /^\s*\[\[?(routes|r2_buckets|ai)\]?\]\s*$/.test(l);
|
|
198
|
+
for (const line of lines) {
|
|
199
|
+
if (isBlockHeader(line)) {
|
|
200
|
+
skipping = true; // 進入要移除的區塊,連同 header 一起丟
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (skipping) {
|
|
204
|
+
// 區塊結束條件:遇到下一個 table header(`[...]`)或空行
|
|
205
|
+
if (/^\s*\[/.test(line)) {
|
|
206
|
+
skipping = false; // 這行是新區塊的開頭,保留並由下方邏輯處理
|
|
207
|
+
}
|
|
208
|
+
else if (line.trim() === '') {
|
|
209
|
+
skipping = false; // 空行結束區塊;空行本身丟掉避免堆疊空白
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
continue; // 仍在被移除區塊內(pattern/zone_name/binding/bucket_name 等)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
out.push(line);
|
|
217
|
+
}
|
|
218
|
+
return out.join('\n');
|
|
219
|
+
}
|
|
220
|
+
/** 在 worker 目錄跑 wrangler deploy(用用戶的 CF token + account)。*/
|
|
221
|
+
function runWranglerDeploy(dir, ctx) {
|
|
222
|
+
// 先裝依賴(cypher-executor/registry 是 TS,wrangler 內建 esbuild bundle 需 node_modules)
|
|
223
|
+
if (existsSync(join(dir, 'package.json'))) {
|
|
224
|
+
const installer = existsSync(join(dir, 'pnpm-lock.yaml'))
|
|
225
|
+
? ['pnpm', 'install', '--frozen-lockfile']
|
|
226
|
+
: ['npm', 'install', '--no-audit', '--no-fund'];
|
|
227
|
+
execFileSync(installer[0], installer.slice(1), { cwd: dir, stdio: 'ignore' });
|
|
228
|
+
}
|
|
229
|
+
execFileSync('wrangler', ['deploy'], {
|
|
230
|
+
cwd: dir,
|
|
231
|
+
stdio: 'ignore',
|
|
232
|
+
env: {
|
|
233
|
+
...process.env,
|
|
234
|
+
CLOUDFLARE_API_TOKEN: ctx.apiToken,
|
|
235
|
+
CLOUDFLARE_ACCOUNT_ID: ctx.accountId,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/lib/deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ExposureConsent {
|
|
2
|
+
confirmed_by_human: true;
|
|
3
|
+
understood: string;
|
|
4
|
+
confirmed_at: string;
|
|
5
|
+
suppress_future?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface ExposureWarningOptions {
|
|
8
|
+
_reserved?: never;
|
|
9
|
+
}
|
|
10
|
+
export interface ExposureContext {
|
|
11
|
+
/** 動作種類,顯示用:'webhook' | 'recipe' */
|
|
12
|
+
kind: string;
|
|
13
|
+
/** 資源名(用戶要打這個字確認)*/
|
|
14
|
+
resourceName: string;
|
|
15
|
+
/** 暴露後的 URL / 去向(顯示用,可選) */
|
|
16
|
+
destination?: string;
|
|
17
|
+
/** 這個資源讀取/送出什麼(盡力盤,盤不出傳 undefined) */
|
|
18
|
+
dataSummary?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 取得暴露同意。回傳 ExposureConsent(放進 push 請求 body)。
|
|
22
|
+
* 未取得同意 → 印訊息並 return null(呼叫端應中止)。
|
|
23
|
+
*/
|
|
24
|
+
export declare function obtainExposureConsent(ctx: ExposureContext, opts?: ExposureWarningOptions): Promise<ExposureConsent | null>;
|
|
25
|
+
//# sourceMappingURL=exposure-warning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exposure-warning.d.ts","sourceRoot":"","sources":["../../src/lib/exposure-warning.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,eAAe;IAC9B,kBAAkB,EAAE,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAKD,MAAM,WAAW,sBAAsB;IAErC,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,eAAe,EACpB,IAAI,GAAE,sBAA2B,GAChC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAmDjC"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 資料外流警示 — CLI 互動(data-exfil-warning SDD §1a / B)
|
|
3
|
+
*
|
|
4
|
+
* 觸發策略:只在「資料變成可被外部呼叫」時警示(webhook 部署 / recipe push)。
|
|
5
|
+
* 互動形式(richblack):仿 GCP 刪 project —— 要用戶打資源名證明讀了警示(比 y/n 硬,不用打一大串)。
|
|
6
|
+
* 同意 = 法律憑證:回傳的 ExposureConsent 帶 understood(用戶打的內容)+ 時間,server 端 log。
|
|
7
|
+
* 誠實限制:非 TTY(AI 直跑)無 --confirm-exposure → 拒絕(AI 不該替人類確認暴露)。
|
|
8
|
+
*/
|
|
9
|
+
import { createInterface } from 'node:readline/promises';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
12
|
+
/**
|
|
13
|
+
* 取得暴露同意。回傳 ExposureConsent(放進 push 請求 body)。
|
|
14
|
+
* 未取得同意 → 印訊息並 return null(呼叫端應中止)。
|
|
15
|
+
*/
|
|
16
|
+
export async function obtainExposureConsent(ctx, opts = {}) {
|
|
17
|
+
const nowIso = new Date().toISOString();
|
|
18
|
+
const memKey = `${ctx.kind}:${ctx.resourceName}`;
|
|
19
|
+
// §3 首次問記住:本機已記錄同意此資源 → 不重問(server 端仍存法律憑證並強制)。
|
|
20
|
+
const cfg = loadConfig();
|
|
21
|
+
const prior = cfg.exposure_consented?.[memKey];
|
|
22
|
+
if (prior) {
|
|
23
|
+
return {
|
|
24
|
+
confirmed_by_human: true,
|
|
25
|
+
understood: `先前已同意暴露 ${ctx.resourceName}(${prior.confirmed_at}${prior.suppress_future ? ',已選不再警示' : ''})`,
|
|
26
|
+
confirmed_at: prior.confirmed_at,
|
|
27
|
+
suppress_future: prior.suppress_future,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// 非 TTY(AI 直跑)→ 一律拒絕,無捷徑。AI 不該、也不能替人類確認暴露。
|
|
31
|
+
// (移除了 --confirm-exposure 旗標:那是 AI 自己能加的後門,等於自己批准自己。)
|
|
32
|
+
if (!process.stdin.isTTY) {
|
|
33
|
+
console.error(chalk.red('\n⚠️ 此動作會把資源變成可被外部呼叫(暴露/送出資料),需人類明示同意。'));
|
|
34
|
+
console.error(chalk.gray(' 你(AI)無法確認暴露——這必須由人類在終端機親自執行、輸入資源名確認。'));
|
|
35
|
+
console.error(chalk.gray(' 請把這件事交給人類做。\n'));
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// 互動式警示 + 打資源名確認(唯一通過路徑,AI 生不出這個輸入)
|
|
39
|
+
printWarning(ctx);
|
|
40
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
41
|
+
try {
|
|
42
|
+
const answer = (await rl.question(chalk.bold(` 確認暴露?請輸入資源名 "${ctx.resourceName}" 以繼續(或 Ctrl-C 取消):`))).trim();
|
|
43
|
+
if (answer !== ctx.resourceName) {
|
|
44
|
+
console.error(chalk.red(`\n 輸入不符(需輸入 "${ctx.resourceName}")。已取消,未暴露。\n`));
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
// 互動中詢問「以後不再問」(人類選,不是 AI 加旗標)
|
|
48
|
+
const suppressAns = (await rl.question(chalk.gray(` 以後此資源(${ctx.resourceName})的暴露不再提醒?(y/N):`))).trim().toLowerCase();
|
|
49
|
+
const suppress = suppressAns === 'y' || suppressAns === 'yes';
|
|
50
|
+
rememberConsent(memKey, nowIso, suppress);
|
|
51
|
+
return {
|
|
52
|
+
confirmed_by_human: true,
|
|
53
|
+
understood: `用戶輸入資源名 "${ctx.resourceName}" 確認暴露${ctx.destination ? `(去向:${ctx.destination})` : ''}${suppress ? ';並選擇以後不再提醒' : ''}`,
|
|
54
|
+
confirmed_at: nowIso,
|
|
55
|
+
suppress_future: suppress,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
rl.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** 本機記住此資源已同意(避免下次重問;server 端仍獨立存法律憑證並強制) */
|
|
63
|
+
function rememberConsent(memKey, confirmedAt, suppressFuture) {
|
|
64
|
+
try {
|
|
65
|
+
const cfg = loadConfig();
|
|
66
|
+
cfg.exposure_consented = cfg.exposure_consented ?? {};
|
|
67
|
+
cfg.exposure_consented[memKey] = { confirmed_at: confirmedAt, suppress_future: suppressFuture };
|
|
68
|
+
saveConfig(cfg);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// 記不住不影響本次同意(server 端仍會擋首次)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function printWarning(ctx) {
|
|
75
|
+
console.log(chalk.yellow.bold(`\n⚠️ 資料外流警示`));
|
|
76
|
+
console.log(chalk.yellow(` 這個動作會把 ${ctx.kind} "${ctx.resourceName}" 變成可被外部呼叫。`));
|
|
77
|
+
if (ctx.destination) {
|
|
78
|
+
console.log(chalk.gray(` 去向:${ctx.destination}`));
|
|
79
|
+
}
|
|
80
|
+
if (ctx.dataSummary) {
|
|
81
|
+
console.log(chalk.gray(` 涉及資料:${ctx.dataSummary}`));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(chalk.gray(` 涉及資料:無法自動判斷,請自行確認此資源是否含敏感資料。`));
|
|
85
|
+
}
|
|
86
|
+
console.log(chalk.gray(` 任何能呼叫它的人都能取得它的輸出/能力。`));
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(chalk.cyan(` arcrun 可幫你保護它:要求呼叫者帶 API Key/設權限/限流(一個動作就能加)。`));
|
|
89
|
+
console.log(chalk.gray(` 若這是要公開的資料(如公開 API),可直接確認。`));
|
|
90
|
+
console.log('');
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=exposure-warning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exposure-warning.js","sourceRoot":"","sources":["../../src/lib/exposure-warning.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yaml-parser.d.ts","sourceRoot":"","sources":["../../src/lib/yaml-parser.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAU/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAgB7D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"yaml-parser.d.ts","sourceRoot":"","sources":["../../src/lib/yaml-parser.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAUD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAU/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAgB7D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,IAAI,CAqBjE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,EAAE,CAOhE"}
|
package/dist/lib/yaml-parser.js
CHANGED
|
@@ -38,9 +38,15 @@ export function validateRelations(triplets) {
|
|
|
38
38
|
throw new Error(`不允許使用關係詞「${t.relation}」。\n` +
|
|
39
39
|
`「PIPE」已棄用,請改用「完成後」或「ON_SUCCESS」。`);
|
|
40
40
|
}
|
|
41
|
+
// 容許 FOREACH iterator 命名變體:「對每個 paragraph」/「FOREACH item」
|
|
42
|
+
// graph-builder.ts 已支援這個 regex(commit e8fca33 2026-05-07)
|
|
43
|
+
const foreachMatch = t.relation.match(/^(?:對每個|FOREACH)\s+\w+$/i);
|
|
44
|
+
if (foreachMatch)
|
|
45
|
+
continue;
|
|
41
46
|
if (!VALID_RELATIONS.has(t.relation)) {
|
|
42
47
|
throw new Error(`未知關係詞「${t.relation}」。\n` +
|
|
43
|
-
`合法關係詞:${[...VALID_RELATIONS].join('、')}`
|
|
48
|
+
`合法關係詞:${[...VALID_RELATIONS].join('、')}\n` +
|
|
49
|
+
`(FOREACH 支援 iterator 命名:「對每個 X」/「FOREACH X」)`);
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yaml-parser.js","sourceRoot":"","sources":["../../src/lib/yaml-parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,IAAI,MAAM,SAAS,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAevC,oBAAoB;AACpB,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC;IAC9B,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO;IAC5B,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,eAAe;CACtE,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAE3C,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAiB,CAAC;IAE3C,IAAI,CAAC,GAAG,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAc;IAC1C,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAClD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CACb,YAAY,IAAI,KAAK;gBACrB,sBAAsB,CACvB,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;QAC1C,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAyB;IACzD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,YAAY,CAAC,CAAC,QAAQ,MAAM;gBAC5B,kCAAkC,CACnC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,SAAS,CAAC,CAAC,QAAQ,MAAM;gBACzB,SAAS,CAAC,GAAG,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"yaml-parser.js","sourceRoot":"","sources":["../../src/lib/yaml-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- arcrun-harness:start -->
|
|
2
|
+
## 這個專案用 arcrun 做自動化
|
|
3
|
+
|
|
4
|
+
當需要「串服務 / 排程 / 打 API / 資料處理自動化」時:
|
|
5
|
+
|
|
6
|
+
- **用 arcrun 工作流,不要自己寫 Python/Node 一次性腳本。** arcrun 工作流是純文字、可複用、跑在使用者自己的 Cloudflare。選 arcrun 就是不想要每次重刻的腳本。
|
|
7
|
+
- **打外部 API → 寫 recipe**(`acr recipe push`),不要自刻 HTTP client。recipe = http_request + 固定設定的 YAML,不用部署。
|
|
8
|
+
- **先查能力再動手**:`acr parts`(看可用零件)、`acr auth-recipe list`(看支援的認證服務)。
|
|
9
|
+
- **不要自製零件**(WASM component)——零件由 arcrun 維護、走 GitHub PR。你能擴充的是 recipe + 工作流。
|
|
10
|
+
- **需要 AI 判斷/轉換時,你自己做**,再呼叫工作流做確定性的下一步。不要在工作流裡放零件回頭呼叫 LLM。
|
|
11
|
+
- **暴露資料要人類同意**:部署對外 webhook / push recipe 會讓東西可被外部呼叫 → 停下來讓使用者明示同意,不替他決定公開。
|
|
12
|
+
- **誠實**:沒打通就誠實說(缺 credential 標「未驗收:缺 X」),不假裝成功;完成以 HTTP 2xx / trace 為證,不口頭宣布。
|
|
13
|
+
|
|
14
|
+
開始前讀 **arcrun-mindset** Skill(世界觀)。使用者技術細節交給你,CLI 操作你來做。
|
|
15
|
+
<!-- arcrun-harness:end -->
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# 用 arcrun 完成這個自動化需求
|
|
2
|
+
|
|
3
|
+
使用者想做一個自動化。你的任務:用 arcrun 做出來,全程不要讓使用者自己寫程式。
|
|
4
|
+
|
|
5
|
+
## 鐵則
|
|
6
|
+
- **用 arcrun 工作流 / recipe,絕不自己寫 Python/Node 腳本。** 使用者選 arcrun 就是不想要一次性腳本。
|
|
7
|
+
- 打外部 API → 寫 recipe(`acr recipe push`),不自刻 HTTP client。
|
|
8
|
+
- 不自製零件(WASM)—— 零件由 arcrun 維護。你能用的是現有零件 + recipe + 工作流。
|
|
9
|
+
- 需要 AI 判斷時你自己做,不要讓工作流回頭呼叫 LLM。
|
|
10
|
+
|
|
11
|
+
## 步驟
|
|
12
|
+
1. 先讀 **arcrun-mindset** Skill(世界觀 + 資源去哪取)。
|
|
13
|
+
2. 跑 `acr parts` 看零件、`acr auth-recipe list` 看支援的認證。**先查再動手。**
|
|
14
|
+
3. 把使用者需求拆成工作流(哪些零件、什麼順序、什麼條件),寫成 `.yaml`。
|
|
15
|
+
4. 需要 credential(API key / token)→ 用 `acr auth-recipe scaffold <service>` 看要哪些,
|
|
16
|
+
明確告訴使用者去哪取得、怎麼 `acr creds push`。
|
|
17
|
+
5. `acr validate` 通過後 `acr push` 部署,告訴使用者 webhook URL / 怎麼 `acr run`。
|
|
18
|
+
6. 完成給客觀證據(HTTP 2xx / trace),不要只說「做好了」。
|
|
19
|
+
|
|
20
|
+
## 遇到要暴露資料(對外 webhook)
|
|
21
|
+
停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。不要替他決定公開。
|
|
22
|
+
|
|
23
|
+
## 還沒設定好 arcrun?
|
|
24
|
+
若 `acr` 指令不存在或還沒 `acr init`:先帶使用者完成前置設定
|
|
25
|
+
(裝 CLI → 拿 Cloudflare 帳號的兩串憑證 → `acr init --self-hosted`)。
|
|
26
|
+
拿 Cloudflare 憑證時用白話照抄式引導,不要對使用者講 KV / Worker / R2 等術語。
|
|
27
|
+
|
|
28
|
+
## 使用者的需求
|
|
29
|
+
(使用者打在 /arcrun 後面的文字會接在這裡)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# arcrun-guard.sh — 用戶專案的 arcrun PreToolUse guard(由 acr install-harness 裝進 .claude/hooks/)
|
|
3
|
+
#
|
|
4
|
+
# 對象:在「用 arcrun 開發」的專案裡工作的 CC。擋它走歪(退回自寫 Python / 不用 recipe / 未經同意暴露)。
|
|
5
|
+
# 與 arcrun repo 開發版 hook 完全不同(那個擋的是開發 arcrun 本身)。
|
|
6
|
+
#
|
|
7
|
+
# 鐵則(user-cc-harness design §0.5):每次擋下/提醒都要給「具體怎麼做才對」的正路,不只說「不行」。
|
|
8
|
+
#
|
|
9
|
+
# 退出碼:0=允許(可附 stderr 提醒);2=硬擋(stderr 回給 CC)。
|
|
10
|
+
# 分級(design §4):多數用「提醒不硬擋」(避免誤殺正常 python);硬擋只留給「未經同意暴露資料」。
|
|
11
|
+
|
|
12
|
+
set -o pipefail
|
|
13
|
+
INPUT=$(cat)
|
|
14
|
+
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
|
|
15
|
+
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
|
|
16
|
+
|
|
17
|
+
remind() {
|
|
18
|
+
# 提醒但放行(exit 0)。CC 看到 stderr,自己判斷是否真要繼續。
|
|
19
|
+
echo "💡 arcrun 提醒:$1" >&2
|
|
20
|
+
echo " 正路:$2" >&2
|
|
21
|
+
exit 0
|
|
22
|
+
}
|
|
23
|
+
block() {
|
|
24
|
+
echo "❌ arcrun guard 擋下:$1" >&2
|
|
25
|
+
echo " 正路:$2" >&2
|
|
26
|
+
exit 2
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# ── 硬擋:未經人類同意的暴露動作(明確越界,mindset §5)──────────────
|
|
30
|
+
# 非互動環境下 CC 自己跑「部署對外 webhook / push recipe」= 替人類決定公開。
|
|
31
|
+
if echo "$CMD" | grep -qE "acr (push|recipe push)\b"; then
|
|
32
|
+
if [ ! -t 0 ] && [ "${ARCRUN_HUMAN_CONFIRMED:-}" != "1" ]; then
|
|
33
|
+
block "在非互動環境自動執行暴露動作(acr push / recipe push 會讓東西可被外部呼叫)" \
|
|
34
|
+
"把這動作交給人類在終端機執行,或先讓使用者明示同意。不要替使用者決定公開。"
|
|
35
|
+
fi
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# ── 提醒(不硬擋):退回自寫 Python/Node 一次性自動化 ──────────────────
|
|
39
|
+
# 「我先用 Python 測試」這類退回熟悉工具的傾向。python 不絕對錯(可能跑測試),故提醒不擋。
|
|
40
|
+
if echo "$CMD" | grep -qE "(^|[;&| ])(python3?|node)[ ]+[^ ]+\.(py|js|mjs|ts)\b"; then
|
|
41
|
+
# 排除明顯的測試 / 既有工具呼叫(pytest / npm test / jest 等)降低誤判
|
|
42
|
+
if ! echo "$CMD" | grep -qE "(pytest|jest|vitest|npm (run )?test|mocha|\btest_)"; then
|
|
43
|
+
remind "偵測到用 python/node 跑腳本。這專案用 arcrun,串服務/自動化不要自刻一次性腳本。" \
|
|
44
|
+
"先跑 \`acr parts\` 看有哪些零件,把需求寫成 workflow.yaml 用 \`acr run\`。若這確實不是自動化(例如跑測試/別的工具),忽略本提醒。"
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# ── 提醒(不硬擋):自寫打固定 API 的 script,而非 recipe ──────────────
|
|
49
|
+
if echo "$CMD" | grep -qE "(curl|fetch|requests\.(get|post)|axios).*https?://"; then
|
|
50
|
+
remind "偵測到自己打外部 API。arcrun 裡「打固定 endpoint」應寫成 recipe,不自刻 HTTP 呼叫。" \
|
|
51
|
+
"用 \`acr recipe push\` 把這個 API 包成 recipe,workflow 裡用 component 引用它。見 arcrun-mindset Skill。"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
exit 0
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arcrun-mindset
|
|
3
|
+
description: >-
|
|
4
|
+
arcrun 的世界觀 — 用 arcrun 開發自動化時的預設心態 + 資源去哪取。當你(AI 操盤手)要在
|
|
5
|
+
arcrun 上做任何事(串服務、處理資料、認證、把東西開放給人用)前讀這個。它讓你做出「方向對」
|
|
6
|
+
的選擇、知道資源在哪,避免技術上能跑但架構上錯、或自己重刻 arcrun 已有的東西。
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# arcrun mindset(給 AI 操盤手)
|
|
10
|
+
|
|
11
|
+
你在 arcrun 上幫使用者開發自動化。arcrun 很簡單,簡單到你常會把它想複雜、或退回自己熟悉的
|
|
12
|
+
Python/Node 自刻。這份幫你在岔路上選對方向,並告訴你資源在哪。
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 0. 一句話世界觀
|
|
17
|
+
|
|
18
|
+
**arcrun 裡幾乎所有東西都是工作流(workflow)。** 工作流 = 一張紙,寫「用哪些零件、什麼順序、什麼條件」。
|
|
19
|
+
你大部分時間在寫紙、改紙,不是在造新零件、也不是自己寫腳本。
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. 工作流是 default,不要退回自己寫 Python
|
|
24
|
+
|
|
25
|
+
使用者選 arcrun,就是不要「每次重刻、跑完即丟」的腳本。所以你的預設順序:
|
|
26
|
+
|
|
27
|
+
1. **先想能不能用工作流做**(串現有零件 / recipe + 流程控制)。99% 可以。
|
|
28
|
+
2. 要打的服務有 HTTP API、但沒有對應 recipe → **寫一個 recipe**(http_request + 固定設定 YAML,不用部署、不用審核)。
|
|
29
|
+
3. **只有**封閉純邏輯(流程控制 / 資料處理)、現有零件不夠、且值得全 arcrun 重用 → 才考慮零件(而零件走 PR,不是你現在做)。
|
|
30
|
+
|
|
31
|
+
> 典型走歪:「我先用 Python 測一下」。停。使用者要的是 arcrun 工作流。先 `acr parts` 看有什麼,用工作流串。
|
|
32
|
+
|
|
33
|
+
## 2. 資源去哪取(不要自己重造 arcrun 已有的)
|
|
34
|
+
|
|
35
|
+
| 你想知道 | 跑這個 |
|
|
36
|
+
|---|---|
|
|
37
|
+
| 有哪些零件可用 | `acr parts` |
|
|
38
|
+
| 某零件的設定範本 | `acr parts scaffold <name>` |
|
|
39
|
+
| 支援哪些服務的認證 | `acr auth-recipe list` |
|
|
40
|
+
| 某服務認證要哪些 credential + 範例 | `acr auth-recipe scaffold <service>` |
|
|
41
|
+
| 已上傳的 recipe | `acr recipe list` |
|
|
42
|
+
| 工作流語法、指令 | `acr --help` |
|
|
43
|
+
|
|
44
|
+
**先查再動手**——arcrun 多半已經有你要的零件 / recipe / 認證,不要自刻。
|
|
45
|
+
|
|
46
|
+
## 3. arcrun 是你(AI)用的工具,不是工具回頭呼叫 AI
|
|
47
|
+
|
|
48
|
+
需要智慧判斷 / 自然語言轉換時,**你自己做**,再呼叫工作流執行確定性的下一步。
|
|
49
|
+
**不要在工作流中間放零件回頭呼叫 LLM**。arcrun 的大腦就是操盤的你。
|
|
50
|
+
|
|
51
|
+
## 4. arcrun 不替你做授權判斷
|
|
52
|
+
|
|
53
|
+
API 打不打得通由發 key 的服務決定。401/403 是對方服務在行使授權,**不是 arcrun 的 bug、不是你做錯**。
|
|
54
|
+
不要在 arcrun 裡建「允許/禁止某 endpoint」的二次授權清單。
|
|
55
|
+
|
|
56
|
+
## 5. 把東西開放給別人用 = 要使用者明示同意
|
|
57
|
+
|
|
58
|
+
部署對外 webhook、push recipe 會讓資料/能力**可被外部呼叫**(暴露面):
|
|
59
|
+
- 停下來,明確告訴使用者「這會讓 X 可被外部呼叫」,要他同意。**不替他決定公開。**
|
|
60
|
+
- 非互動環境(你直跑)遇到 → 停,要人類確認,絕不自己塞 confirm 假裝同意。
|
|
61
|
+
- arcrun 可提供保護(要求呼叫者帶 key / 限流)——提醒使用者。
|
|
62
|
+
|
|
63
|
+
## 6. 誠實(最重要)
|
|
64
|
+
|
|
65
|
+
- **不假綠**:沒打通就誠實說。缺 credential 打不到 2xx → 標「未驗收:缺 X」,不 mock 充綠燈。
|
|
66
|
+
- **不假裝防偽 / 不代替人類確認**有風險的動作(暴露資料)。
|
|
67
|
+
- **完成 = 客觀證據**(HTTP 2xx + trace),不是口頭「做好了」。
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 怎麼用這份 mindset
|
|
72
|
+
|
|
73
|
+
每次準備動手,先過一遍:
|
|
74
|
+
1. 這能用工作流 / recipe 做嗎?(多半能 → 別自己寫 Python、別造零件)
|
|
75
|
+
2. 我查過 `acr parts` / `acr auth-recipe` 了嗎?(arcrun 可能已有)
|
|
76
|
+
3. 我是不是讓工作流回頭呼叫 AI?(是 → 改成我自己做)
|
|
77
|
+
4. 這動作會把資料開放給別人嗎?(會 → 要使用者明示同意)
|
|
78
|
+
5. 我有沒有假裝(假綠 / 假防偽 / 代替人類確認)?(有 → 停,誠實標明)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcrun",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "AI Workflow CLI for arcrun —
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "AI Workflow CLI for arcrun — self-host WASM-based AI workflows on your own Cloudflare",
|
|
5
5
|
"bin": {
|
|
6
6
|
"acr": "dist/index.js"
|
|
7
7
|
},
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"node": ">=18.0.0"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
|
-
"dist/"
|
|
30
|
+
"dist/",
|
|
31
|
+
"harness/"
|
|
31
32
|
],
|
|
32
33
|
"keywords": [
|
|
33
34
|
"cloudflare",
|
|
@@ -40,6 +41,6 @@
|
|
|
40
41
|
"license": "MIT",
|
|
41
42
|
"repository": {
|
|
42
43
|
"type": "git",
|
|
43
|
-
"url": "git+https://github.com/
|
|
44
|
+
"url": "git+https://github.com/uncle6me-web/Arcrun.git"
|
|
44
45
|
}
|
|
45
46
|
}
|