@windsland52/maafw-live 0.3.0 → 0.4.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.
- package/README.md +24 -9
- package/docs/daemon-protocol.md +15 -10
- package/docs/keyframe-retention-contract-v0.md +23 -34
- package/docs/session-unify-v0.md +115 -0
- package/lib/client/daemon.js +45 -3
- package/lib/commands/annotate.js +15 -8
- package/lib/commands/calibrate.js +3 -3
- package/lib/commands/color.js +3 -3
- package/lib/commands/crop.js +8 -8
- package/lib/commands/dispatch.js +121 -0
- package/lib/commands/env.js +200 -6
- package/lib/commands/index.js +7 -1
- package/lib/commands/input.js +71 -33
- package/lib/commands/kf.js +49 -13
- package/lib/commands/probe.js +70 -0
- package/lib/commands/reco.js +285 -56
- package/lib/commands/repl.js +1305 -502
- package/lib/commands/roi-check.js +273 -0
- package/lib/commands/roi-compare.js +388 -0
- package/lib/commands/runtime.js +295 -91
- package/lib/commands/timing.js +28 -10
- package/lib/daemon/framed.mjs +1014 -184
- package/lib/daemon/reco_child.mjs +150 -9
- package/lib/flags.js +2 -0
- package/lib/index.js +24 -20
- package/lib/interface/hotkey.js +142 -0
- package/lib/interface/index.js +1 -1
- package/lib/interface/load.js +107 -17
- package/lib/interface/override.js +218 -34
- package/lib/interface/plan.js +108 -17
- package/lib/option-args.js +29 -0
- package/lib/pkg.js +36 -0
- package/lib/protocol.js +4 -0
- package/lib/runtime/actions.js +127 -12
- package/lib/runtime/log-watch.js +160 -0
- package/lib/runtime/node-source.js +100 -0
- package/lib/runtime/ocr-model.js +45 -0
- package/lib/runtime/pi-env.js +34 -0
- package/lib/runtime/pretask.js +217 -0
- package/lib/runtime/prune.js +28 -0
- package/lib/runtime/receipts.js +57 -0
- package/lib/runtime/reco-args.js +38 -0
- package/lib/runtime/reco-verdict.js +82 -0
- package/lib/runtime/roi-compare.js +198 -0
- package/lib/runtime/session-forward.js +79 -0
- package/lib/runtime/session-ipc.js +471 -0
- package/lib/runtime/session.js +57 -8
- package/lib/runtime/signal-guard.js +35 -0
- package/lib/types/commands/dispatch.d.ts +22 -0
- package/lib/types/commands/env.d.ts +19 -0
- package/lib/types/commands/index.d.ts +1 -1
- package/lib/types/commands/probe.d.ts +15 -0
- package/lib/types/commands/reco.d.ts +11 -7
- package/lib/types/commands/repl.d.ts +8 -0
- package/lib/types/commands/roi-check.d.ts +3 -0
- package/lib/types/commands/roi-compare.d.ts +3 -0
- package/lib/types/commands/runtime.d.ts +20 -2
- package/lib/types/flags.d.ts +4 -0
- package/lib/types/interface/hotkey.d.ts +36 -0
- package/lib/types/interface/index.d.ts +1 -1
- package/lib/types/interface/load.d.ts +84 -7
- package/lib/types/interface/override.d.ts +58 -13
- package/lib/types/interface/plan.d.ts +23 -2
- package/lib/types/option-args.d.ts +19 -0
- package/lib/types/pkg.d.ts +8 -0
- package/lib/types/protocol.d.ts +16 -0
- package/lib/types/runtime/actions.d.ts +89 -7
- package/lib/types/runtime/log-watch.d.ts +28 -0
- package/lib/types/runtime/node-source.d.ts +25 -0
- package/lib/types/runtime/ocr-model.d.ts +8 -0
- package/lib/types/runtime/pi-env.d.ts +24 -0
- package/lib/types/runtime/pretask.d.ts +53 -0
- package/lib/types/runtime/prune.d.ts +4 -0
- package/lib/types/runtime/receipts.d.ts +7 -0
- package/lib/types/runtime/reco-args.d.ts +18 -0
- package/lib/types/runtime/reco-verdict.d.ts +50 -0
- package/lib/types/runtime/roi-compare.d.ts +117 -0
- package/lib/types/runtime/session-forward.d.ts +10 -0
- package/lib/types/runtime/session-ipc.d.ts +117 -0
- package/lib/types/runtime/session.d.ts +26 -2
- package/lib/types/runtime/signal-guard.d.ts +19 -0
- package/lib/usage.js +3 -3
- package/package.json +17 -4
- package/skills/maafw-live/SKILL.md +32 -2
- package/skills/maafw-live/references/pitfalls.md +97 -4
- package/skills/maafw-live/references/workflows.md +5 -0
- package/docs/keyframe-evidence-contract-v0.md +0 -61
- package/lib/client/daemon.js.map +0 -1
- package/lib/commands/annotate.js.map +0 -1
- package/lib/commands/calibrate.js.map +0 -1
- package/lib/commands/color.js.map +0 -1
- package/lib/commands/crop.js.map +0 -1
- package/lib/commands/env.js.map +0 -1
- package/lib/commands/index.js.map +0 -1
- package/lib/commands/input.js.map +0 -1
- package/lib/commands/kf.js.map +0 -1
- package/lib/commands/reco.js.map +0 -1
- package/lib/commands/repl.js.map +0 -1
- package/lib/commands/runtime.js.map +0 -1
- package/lib/commands/skill.js.map +0 -1
- package/lib/commands/timing.js.map +0 -1
- package/lib/commands/version.js.map +0 -1
- package/lib/exec.js.map +0 -1
- package/lib/flags.js.map +0 -1
- package/lib/image.js.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/interface/index.js.map +0 -1
- package/lib/interface/jsonc.js.map +0 -1
- package/lib/interface/load.js.map +0 -1
- package/lib/interface/override.js.map +0 -1
- package/lib/interface/plan.js.map +0 -1
- package/lib/pkg.js.map +0 -1
- package/lib/protocol.js.map +0 -1
- package/lib/runtime/actions.js.map +0 -1
- package/lib/runtime/keyframes.js.map +0 -1
- package/lib/runtime/session.js.map +0 -1
- package/lib/update-check.js.map +0 -1
- package/lib/usage.js.map +0 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* reco_child —— 识别单测的一次性子进程(崩溃隔离)。
|
|
3
3
|
* 用法:node reco_child.mjs --cfg <json> --out <result.json>
|
|
4
|
-
* cfg = { resourceDir, type, image, cases?, node?, templateImage? }
|
|
4
|
+
* cfg = { resourceDirs? | resourceDir, type, image, cases?, node?, nodeName?, templateImage? }
|
|
5
5
|
* cases: 父进程(framed.mjs)已折叠好的参数用例列表,逐例执行
|
|
6
6
|
* node : 整节点 JSON 原样透传(V1 扁平 / V2 嵌套均支持,不转换)
|
|
7
|
+
* nodeName: 按资源里的原名跑(不 dump 注入——dump 是缺省填满的 V2,与手写稀疏 V1 不是同一份)
|
|
7
8
|
* param/sweep: 直接调用(调试)时的单例路径,与 cases 二选一
|
|
8
9
|
*
|
|
9
10
|
* 为什么要造一个"图像假控制器":裸 post_recognition 不产出 RecoId,于是 recognition_detail 拿不到
|
|
@@ -78,6 +79,78 @@ function strip(detail) {
|
|
|
78
79
|
return copy
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function writeOut(payload) {
|
|
83
|
+
fs.writeFileSync(out, JSON.stringify(payload), "utf8")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** dump 里的识别类型:V2 是 `recognition.type`,个别版本/形态可能还是 V1 的字符串。 */
|
|
87
|
+
function recoTypeOf(dump) {
|
|
88
|
+
const rec = dump && dump.recognition
|
|
89
|
+
if (typeof rec === "string") return rec
|
|
90
|
+
if (rec && typeof rec === "object") return String(rec.type ?? "")
|
|
91
|
+
return ""
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function editDistance(a, b) {
|
|
95
|
+
if (a === b) return 0
|
|
96
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i)
|
|
97
|
+
for (let i = 1; i <= a.length; i++) {
|
|
98
|
+
const cur = [i]
|
|
99
|
+
for (let j = 1; j <= b.length; j++) {
|
|
100
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1))
|
|
101
|
+
}
|
|
102
|
+
prev = cur
|
|
103
|
+
}
|
|
104
|
+
return prev[b.length]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 近邻节点名:命令拼错会给建议,节点名拼错同样要给——否则 `--node-name` 写错一个字只会得到
|
|
109
|
+
* "资源里没有这个节点",而正确名字就在资源里。距离阈值随名字长度放宽(长名的单个字符笔误
|
|
110
|
+
* 距离仍是 1;短名放宽到 2 就够,再宽会把无关节点列出来)。
|
|
111
|
+
*/
|
|
112
|
+
function nearestNames(names, target, limit) {
|
|
113
|
+
const want = String(target)
|
|
114
|
+
const cap = Math.max(2, Math.min(4, Math.floor(want.length / 3)))
|
|
115
|
+
const scored = []
|
|
116
|
+
for (const n of Array.isArray(names) ? names : []) {
|
|
117
|
+
if (typeof n !== "string" || !n || n === want) continue
|
|
118
|
+
const d = editDistance(n.toLowerCase(), want.toLowerCase())
|
|
119
|
+
if (d <= cap) scored.push({ n, d })
|
|
120
|
+
}
|
|
121
|
+
return scored.sort((x, y) => x.d - y.d || x.n.localeCompare(y.n)).slice(0, limit).map((x) => x.n)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* dump 里的 Custom 识别:顶层 `type: "Custom"` / V1 的 `custom_recognition`,**以及 And / Or 里嵌的
|
|
126
|
+
* 子识别**。只看顶层会让"And 里挂了一个 custom 子识别"落回 miss 或"识别调用报错",看起来像没命中,
|
|
127
|
+
* 而它其实和顶层 custom 是同一条能力边界(reco_test 不桥接 agent 子进程)。
|
|
128
|
+
* 返回命中位置(便于在报错里说清是哪一层),深度与条数都封顶,避免异常 dump 撑爆回执。
|
|
129
|
+
*/
|
|
130
|
+
function customHits(dump) {
|
|
131
|
+
const hits = []
|
|
132
|
+
/* key 要一起带下来:字符串 `"Custom"` 只有落在**识别类型位**上才算(V1 的 `recognition` / V2 的
|
|
133
|
+
* `type`)。整棵子树里任何字符串都认会把 `expected: "Custom"`(OCR 要认的文字)和名字恰好叫
|
|
134
|
+
* Custom 的引用节点误判成 Custom 子识别。 */
|
|
135
|
+
const walk = (v, path, key, depth) => {
|
|
136
|
+
if (hits.length >= 8 || depth > 6 || v === null || v === undefined) return
|
|
137
|
+
if (typeof v === "string") {
|
|
138
|
+
if (v === "Custom" && (key === "recognition" || key === "type")) hits.push(path)
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
if (typeof v !== "object") return
|
|
142
|
+
if (Array.isArray(v)) {
|
|
143
|
+
v.forEach((x, i) => walk(x, path + "[" + i + "]", null, depth + 1))
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
if (typeof v.type === "string" && v.type === "Custom") { hits.push(path + ".type"); return }
|
|
147
|
+
if (v.custom_recognition !== undefined) { hits.push(path + ".custom_recognition"); return }
|
|
148
|
+
for (const [k, x] of Object.entries(v)) walk(x, path === "" ? k : path + "." + k, k, depth + 1)
|
|
149
|
+
}
|
|
150
|
+
walk(dump && dump.recognition, "recognition", "recognition", 0)
|
|
151
|
+
return hits
|
|
152
|
+
}
|
|
153
|
+
|
|
81
154
|
async function main() {
|
|
82
155
|
const maa = require("@maaxyz/maa-node")
|
|
83
156
|
const image = toArrayBuffer(fs.readFileSync(cfg.image))
|
|
@@ -91,8 +164,31 @@ async function main() {
|
|
|
91
164
|
await ctrl.post_connection().wait()
|
|
92
165
|
if (!ctrl.connected) throw new Error("假控制器连接失败")
|
|
93
166
|
|
|
167
|
+
/* 资源链:interface.json 的 resource.path[](+ 控制器 attach_resource_path)按声明顺序叠加,
|
|
168
|
+
* 后一段覆盖前一段——与 cmdRun 的 buildResource 同一语义。只加载单包时,overlay 包里的节点改写、
|
|
169
|
+
* 只存在于 overlay 的模板、被 And/Or 按名引用的子节点全部缺失,测出来的就不是项目里那个节点。 */
|
|
170
|
+
const dirs = Array.isArray(cfg.resourceDirs) && cfg.resourceDirs.length
|
|
171
|
+
? cfg.resourceDirs.map(String)
|
|
172
|
+
: [String(cfg.resourceDir)]
|
|
94
173
|
const res = new maa.Resource()
|
|
95
|
-
|
|
174
|
+
/* 逐段点名失败:整链一起报只说"加载失败",看不出是哪一层读不进(原先是父进程
|
|
175
|
+
* probeResourceChain 先加载一遍做先验——探针挪进这里后只加载一次,失败定位不变)。 */
|
|
176
|
+
for (let i = 0; i < dirs.length; i++) {
|
|
177
|
+
const where = '第 ' + (i + 1) + '/' + dirs.length + ' 段(' + dirs[i] + ')'
|
|
178
|
+
let job = null
|
|
179
|
+
try {
|
|
180
|
+
job = res.post_bundle(dirs[i])
|
|
181
|
+
await job.wait()
|
|
182
|
+
} catch (e) {
|
|
183
|
+
writeOut({ ok: false, code: 'RESOURCE_LOAD', error: '资源链加载失败:' + where + ':' + String(e && e.message || e) })
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
const st = Number(job && job.status)
|
|
187
|
+
if (Number.isFinite(st) && st !== 3000) {
|
|
188
|
+
writeOut({ ok: false, code: 'RESOURCE_LOAD', error: '资源链加载失败:' + where + '返回 status=' + st + '(成功应为 3000)' })
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
}
|
|
96
192
|
/* 面板裁出来的模板走 override_image:不必把它塞进任何资源目录,也不写用户项目。 */
|
|
97
193
|
if (cfg.templateImage) res.override_image("@reco_template", toArrayBuffer(fs.readFileSync(cfg.templateImage)))
|
|
98
194
|
|
|
@@ -101,25 +197,66 @@ async function main() {
|
|
|
101
197
|
tasker.resource = res
|
|
102
198
|
|
|
103
199
|
const nodeMode = cfg.node !== undefined
|
|
200
|
+
const nodeNameMode = typeof cfg.nodeName === "string" && cfg.nodeName !== ""
|
|
104
201
|
const node = nodeMode ? nodeEntry(cfg.node, cfg.templateImage) : null
|
|
105
|
-
|
|
202
|
+
/* --node-name:dump 只进回执给人看,**不注入识别**。dump 是 DumpTask(V2 嵌套 + 缺省值全部
|
|
203
|
+
* 填满),与手写稀疏 V1 JSON 不是同一份;dump 再注入会让"测的到底是不是真节点"多一层不确定性。 */
|
|
204
|
+
let nodeDump = null
|
|
205
|
+
if (nodeNameMode) {
|
|
206
|
+
nodeDump = res.get_node_data_parsed(cfg.nodeName)
|
|
207
|
+
if (!nodeDump) {
|
|
208
|
+
const candidates = nearestNames(res.node_list, cfg.nodeName, 3)
|
|
209
|
+
writeOut({
|
|
210
|
+
ok: false, code: "NODE_MISSING", nodeName: cfg.nodeName, resourceDirs: dirs,
|
|
211
|
+
...(candidates.length ? { candidates } : {}),
|
|
212
|
+
error: "资源里没有名为 " + cfg.nodeName + " 的节点(只查资源合并结果,不含 interface 的 pipeline_override)",
|
|
213
|
+
})
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
/* custom / agent 节点:reco_test 不桥接 agent 子进程(run 才桥接),明确报能力边界而不是让它看起来像 miss。
|
|
217
|
+
* And / Or 里嵌的子识别同样算——它们会在 run_recognition 里被真的走到。 */
|
|
218
|
+
const customs = customHits(nodeDump)
|
|
219
|
+
if (customs.length > 0) {
|
|
220
|
+
const onlyTop = customs.length === 1
|
|
221
|
+
&& (customs[0] === "recognition.type" || customs[0] === "recognition")
|
|
222
|
+
writeOut({
|
|
223
|
+
ok: false, code: "CUSTOM_NODE", nodeName: cfg.nodeName, nodeDump, resourceDirs: dirs, customPaths: customs,
|
|
224
|
+
error: onlyTop
|
|
225
|
+
? "节点 " + cfg.nodeName + " 的识别是 Custom:reco_test 不桥接 custom/agent 识别(run --project 才桥接 agent 子进程)"
|
|
226
|
+
: "节点 " + cfg.nodeName + " 的识别含 Custom 子识别(" + customs.length + " 处:"
|
|
227
|
+
+ customs.slice(0, 3).join(" / ") + (customs.length > 3 ? " …" : "")
|
|
228
|
+
+ "):reco_test 不桥接 custom/agent 识别(run --project 才桥接 agent 子进程)",
|
|
229
|
+
})
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const cases = nodeMode || nodeNameMode ? [node] : casesOf(cfg)
|
|
106
234
|
const results = []
|
|
107
235
|
let slot = null
|
|
108
236
|
let started = Date.now()
|
|
109
237
|
|
|
110
238
|
res.register_custom_action("@reco/run", async self => {
|
|
111
|
-
|
|
112
|
-
|
|
239
|
+
/* 入口三形态:--node-name 用资源原名(无 override);--node 原样透传;--type 用折叠好的一例参数。
|
|
240
|
+
* param 只用于回执展示"测的是哪个/组参数"。 */
|
|
241
|
+
let param = null
|
|
113
242
|
let detail = null
|
|
114
243
|
try {
|
|
115
|
-
|
|
244
|
+
if (nodeNameMode) {
|
|
245
|
+
param = { nodeName: cfg.nodeName }
|
|
246
|
+
detail = await self.context.run_recognition(cfg.nodeName, image)
|
|
247
|
+
} else {
|
|
248
|
+
const entry = nodeMode ? node : { recognition: cfg.type, ...(slot || {}) }
|
|
249
|
+
if (!nodeMode && cfg.templateImage && entry.template === undefined) entry.template = "@reco_template"
|
|
250
|
+
param = entry
|
|
251
|
+
detail = await self.context.run_recognition("@reco/node", image, { "@reco/node": entry })
|
|
252
|
+
}
|
|
116
253
|
} catch (e) {
|
|
117
|
-
results.push({ param
|
|
254
|
+
results.push({ param, ms: Date.now() - started, ok: false, error: String(e && e.message || e), detail: null })
|
|
118
255
|
return true
|
|
119
256
|
}
|
|
120
257
|
/* ok 只认 hit===true:miss 时 run_recognition 仍返回 detail 对象(hit:false、box 全零),
|
|
121
258
|
* 旧判 !!detail 恒真——识别未中会标成命中,制造假观测证据。 */
|
|
122
|
-
results.push({ param
|
|
259
|
+
results.push({ param, ms: Date.now() - started, ok: !!detail && detail.hit === true, detail: strip(detail) })
|
|
123
260
|
return true
|
|
124
261
|
})
|
|
125
262
|
|
|
@@ -135,7 +272,11 @@ async function main() {
|
|
|
135
272
|
try { ctrl.destroy() } catch (e) { /* ignore */ }
|
|
136
273
|
}
|
|
137
274
|
|
|
138
|
-
|
|
275
|
+
writeOut({
|
|
276
|
+
ok: true,
|
|
277
|
+
...(nodeNameMode ? { type: recoTypeOf(nodeDump), nodeName: cfg.nodeName, nodeDump } : { type: cfg.type }),
|
|
278
|
+
results,
|
|
279
|
+
})
|
|
139
280
|
}
|
|
140
281
|
|
|
141
282
|
main()
|
package/lib/flags.js
CHANGED
|
@@ -9,6 +9,8 @@ export const GLOBAL_OPTIONS = {
|
|
|
9
9
|
verbose: { type: 'boolean' },
|
|
10
10
|
help: { type: 'boolean', short: 'h' },
|
|
11
11
|
version: { type: 'boolean', short: 'V' },
|
|
12
|
+
/** 把命令转发到命名会话的常驻 daemon(`repl --session <name>` 起的那个) */
|
|
13
|
+
session: { type: 'string' },
|
|
12
14
|
};
|
|
13
15
|
const DEFAULT_LIMIT = 200;
|
|
14
16
|
/**
|
package/lib/index.js
CHANGED
|
@@ -15,9 +15,11 @@ import { resolve } from 'node:path';
|
|
|
15
15
|
import { pathToFileURL } from 'node:url';
|
|
16
16
|
import { parseArgs } from 'node:util';
|
|
17
17
|
import { findCommand, suggestCommand } from './commands/index.js';
|
|
18
|
+
import { dispatchArgv } from './commands/dispatch.js';
|
|
18
19
|
import { GLOBAL_OPTIONS, readGlobalFlags } from './flags.js';
|
|
19
20
|
import { cliPackage } from './pkg.js';
|
|
20
21
|
import { EXIT, emit, fail } from './protocol.js';
|
|
22
|
+
import { forwardToSession, stripSessionFlag } from './runtime/session-forward.js';
|
|
21
23
|
import { commandUsage, topLevelUsage } from './usage.js';
|
|
22
24
|
function print(lines, toStderr = false) {
|
|
23
25
|
const text = lines.join('\n') + '\n';
|
|
@@ -72,28 +74,28 @@ export async function main(argv) {
|
|
|
72
74
|
print(topLevelUsage(), true);
|
|
73
75
|
return EXIT.USAGE;
|
|
74
76
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
/* cwd 与 --json/--color 先粗解析(全局选项集即可,命令私有选项不在这层消费)。
|
|
78
|
+
* --session 转发发生在 dispatchArgv **之前**:会话侧跑的是同一份命令实现,
|
|
79
|
+
* 本地不该为"转发给会话的命令"先新起一个 daemon 再丢(requiresSession 闸门
|
|
80
|
+
* 在会话侧才有意义——ctx.client 已注入时不拦)。 */
|
|
81
|
+
let rough;
|
|
78
82
|
try {
|
|
79
|
-
|
|
83
|
+
rough = parseArgs({
|
|
80
84
|
args: argv.slice(1),
|
|
81
|
-
options,
|
|
85
|
+
options: GLOBAL_OPTIONS,
|
|
82
86
|
allowPositionals: true,
|
|
83
|
-
strict:
|
|
87
|
+
strict: false,
|
|
84
88
|
});
|
|
85
89
|
}
|
|
86
|
-
catch
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return EXIT.USAGE;
|
|
90
|
+
catch {
|
|
91
|
+
/* 全局层解析不过就让 dispatchArgv 走完整错误路径(BAD_ARGUMENTS 信封) */
|
|
92
|
+
rough = { values: {}, positionals: argv.slice(1) };
|
|
90
93
|
}
|
|
91
|
-
// cwd 先解析,因为 --json 之外的所有输出都要用到它
|
|
92
94
|
let cwd = process.cwd();
|
|
93
|
-
if (typeof
|
|
94
|
-
cwd = resolve(
|
|
95
|
+
if (typeof rough.values.cwd === 'string' && rough.values.cwd !== '') {
|
|
96
|
+
cwd = resolve(rough.values.cwd);
|
|
95
97
|
}
|
|
96
|
-
const flags = readGlobalFlags(
|
|
98
|
+
const flags = readGlobalFlags(rough.values, cwd);
|
|
97
99
|
if (flags.help) {
|
|
98
100
|
print(commandUsage(cmd));
|
|
99
101
|
return EXIT.OK;
|
|
@@ -103,13 +105,15 @@ export async function main(argv) {
|
|
|
103
105
|
emit(cmd.name, result, { json: flags.json, color: flags.color });
|
|
104
106
|
return EXIT.ENV;
|
|
105
107
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
/* `--session <name>`:把整条 argv(剥掉 --session 本体)送进常驻会话执行。
|
|
109
|
+
* `repl` 自己的 --session 是"起/查/收会话"的参数,不是转发旗标,不能拦。 */
|
|
110
|
+
const sessionName = typeof rough.values.session === 'string' ? rough.values.session : undefined;
|
|
111
|
+
if (sessionName && cmd.name !== 'repl') {
|
|
112
|
+
const result = await forwardToSession(sessionName, stripSessionFlag(argv));
|
|
113
|
+
emit(cmd.name, result, { json: flags.json, color: flags.color });
|
|
114
|
+
return result.exitCode;
|
|
112
115
|
}
|
|
116
|
+
const { result } = await dispatchArgv(argv, { cwd });
|
|
113
117
|
emit(cmd.name, result, { json: flags.json, color: flags.color });
|
|
114
118
|
return result.exitCode;
|
|
115
119
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hotkey 占位符 → 当前控制器的虚拟按键码(PI v2.8.0+)。
|
|
3
|
+
*
|
|
4
|
+
* 协议只给规则、不给表:`{名称}` / `{名称}.primary` / `{名称}.modifier1` / `{名称}.modifier2`
|
|
5
|
+
* 要替换成"**当前控制器 type** 支持的虚拟按键码",且"映射规则应与 ClickKey、KeyDown 等动作所用
|
|
6
|
+
* 键码表一致"(MaaFW 5.14.2 `3.3-ProjectInterfaceV2协议`,pinned commit 8061d5bc)。
|
|
7
|
+
*
|
|
8
|
+
* 这里四张表的数值是各 OS **公开的键码常量**,不是谁发明的东西:
|
|
9
|
+
* - `Win32` → Windows Virtual-Key Codes(`VK_*`,见 winuser.h)
|
|
10
|
+
* - `Adb` → Android `KeyEvent`(`KEYCODE_*`)
|
|
11
|
+
* - `MacOS` → Carbon `Events.h`(`kVK_*`,ANSI 键盘物理位置)
|
|
12
|
+
* - `WlRoots`/`Linux` → Linux `input-event-codes.h`(`KEY_*`/evdev)
|
|
13
|
+
*
|
|
14
|
+
* 取值与 MXU 的实现交叉核对过(MXU 是 AGPL-3.0,本仓 MIT:**只核数值,不搬代码**)。
|
|
15
|
+
* 键名按 MXU 的持久化约定规范化(大写、`+` 连接、末段为主键),因为它就是协议里"人类可读
|
|
16
|
+
* 快捷键字符串"的实际来源之一。
|
|
17
|
+
*/
|
|
18
|
+
const WIN32 = {
|
|
19
|
+
BACKSPACE: 0x08, TAB: 0x09, ENTER: 0x0d, SHIFT: 0x10, CTRL: 0x11, ALT: 0x12,
|
|
20
|
+
META: 0x5b, COMMAND: 0x5b, CMD: 0x5b, PAUSE: 0x13, CAPSLOCK: 0x14, ESC: 0x1b, SPACE: 0x20,
|
|
21
|
+
PAGEUP: 0x21, PAGEDOWN: 0x22, END: 0x23, HOME: 0x24,
|
|
22
|
+
LEFT: 0x25, UP: 0x26, RIGHT: 0x27, DOWN: 0x28, INSERT: 0x2d, DELETE: 0x2e,
|
|
23
|
+
'0': 0x30, '1': 0x31, '2': 0x32, '3': 0x33, '4': 0x34,
|
|
24
|
+
'5': 0x35, '6': 0x36, '7': 0x37, '8': 0x38, '9': 0x39,
|
|
25
|
+
A: 0x41, B: 0x42, C: 0x43, D: 0x44, E: 0x45, F: 0x46, G: 0x47, H: 0x48, I: 0x49,
|
|
26
|
+
J: 0x4a, K: 0x4b, L: 0x4c, M: 0x4d, N: 0x4e, O: 0x4f, P: 0x50, Q: 0x51, R: 0x52,
|
|
27
|
+
S: 0x53, T: 0x54, U: 0x55, V: 0x56, W: 0x57, X: 0x58, Y: 0x59, Z: 0x5a,
|
|
28
|
+
F1: 0x70, F2: 0x71, F3: 0x72, F4: 0x73, F5: 0x74, F6: 0x75,
|
|
29
|
+
F7: 0x76, F8: 0x77, F9: 0x78, F10: 0x79, F11: 0x7a, F12: 0x7b,
|
|
30
|
+
};
|
|
31
|
+
const MACOS = {
|
|
32
|
+
BACKSPACE: 0x33, TAB: 0x30, ENTER: 0x24, SHIFT: 0x38, CTRL: 0x3b, ALT: 0x3a,
|
|
33
|
+
META: 0x37, COMMAND: 0x37, CMD: 0x37, CAPSLOCK: 0x39, ESC: 0x35, SPACE: 0x31,
|
|
34
|
+
PAGEUP: 0x74, PAGEDOWN: 0x79, END: 0x77, HOME: 0x73,
|
|
35
|
+
LEFT: 0x7b, UP: 0x7e, RIGHT: 0x7c, DOWN: 0x7d, DELETE: 0x75,
|
|
36
|
+
'0': 0x1d, '1': 0x12, '2': 0x13, '3': 0x14, '4': 0x15,
|
|
37
|
+
'5': 0x17, '6': 0x16, '7': 0x1a, '8': 0x1c, '9': 0x19,
|
|
38
|
+
A: 0x00, B: 0x0b, C: 0x08, D: 0x02, E: 0x0e, F: 0x03, G: 0x05, H: 0x04, I: 0x22,
|
|
39
|
+
J: 0x26, K: 0x28, L: 0x25, M: 0x2e, N: 0x2d, O: 0x1f, P: 0x23, Q: 0x0c, R: 0x0f,
|
|
40
|
+
S: 0x01, T: 0x11, U: 0x20, V: 0x09, W: 0x0d, X: 0x07, Y: 0x10, Z: 0x06,
|
|
41
|
+
F1: 0x7a, F2: 0x78, F3: 0x63, F4: 0x76, F5: 0x60, F6: 0x61,
|
|
42
|
+
F7: 0x62, F8: 0x64, F9: 0x65, F10: 0x6d, F11: 0x67, F12: 0x6f,
|
|
43
|
+
};
|
|
44
|
+
const ADB = {
|
|
45
|
+
BACKSPACE: 67, TAB: 61, ENTER: 66, SHIFT: 59, CTRL: 113, ALT: 57,
|
|
46
|
+
META: 117, COMMAND: 117, CMD: 117, SPACE: 62, ESC: 111, DELETE: 112,
|
|
47
|
+
HOME: 3, END: 123, PAGEUP: 92, PAGEDOWN: 93,
|
|
48
|
+
LEFT: 21, RIGHT: 22, UP: 19, DOWN: 20,
|
|
49
|
+
'0': 7, '1': 8, '2': 9, '3': 10, '4': 11, '5': 12, '6': 13, '7': 14, '8': 15, '9': 16,
|
|
50
|
+
A: 29, B: 30, C: 31, D: 32, E: 33, F: 34, G: 35, H: 36, I: 37, J: 38, K: 39, L: 40,
|
|
51
|
+
M: 41, N: 42, O: 43, P: 44, Q: 45, R: 46, S: 47, T: 48, U: 49, V: 50, W: 51, X: 52,
|
|
52
|
+
Y: 53, Z: 54,
|
|
53
|
+
F1: 131, F2: 132, F3: 133, F4: 134, F5: 135, F6: 136, F7: 137, F8: 138, F9: 139,
|
|
54
|
+
F10: 140, F11: 141, F12: 142,
|
|
55
|
+
};
|
|
56
|
+
const WLROOTS = {
|
|
57
|
+
BACKSPACE: 14, TAB: 15, ENTER: 28, SHIFT: 42, CTRL: 29, ALT: 56,
|
|
58
|
+
META: 125, COMMAND: 125, CMD: 125, SPACE: 57, ESC: 1, DELETE: 111,
|
|
59
|
+
HOME: 102, END: 107, PAGEUP: 104, PAGEDOWN: 109,
|
|
60
|
+
LEFT: 105, RIGHT: 106, UP: 103, DOWN: 108,
|
|
61
|
+
'0': 11, '1': 2, '2': 3, '3': 4, '4': 5, '5': 6, '6': 7, '7': 8, '8': 9, '9': 10,
|
|
62
|
+
A: 30, B: 48, C: 46, D: 32, E: 18, F: 33, G: 34, H: 35, I: 23, J: 36, K: 37, L: 38,
|
|
63
|
+
M: 50, N: 49, O: 24, P: 25, Q: 16, R: 19, S: 31, T: 20, U: 22, V: 47, W: 17, X: 45,
|
|
64
|
+
Y: 21, Z: 44,
|
|
65
|
+
F1: 59, F2: 60, F3: 61, F4: 62, F5: 63, F6: 64, F7: 65, F8: 66, F9: 67, F10: 68,
|
|
66
|
+
F11: 87, F12: 88,
|
|
67
|
+
};
|
|
68
|
+
/** 同义键名归一:用户手写的组合键五花八门,认得出就认,认不出会在下面报"未知按键"。 */
|
|
69
|
+
const ALIASES = {
|
|
70
|
+
CONTROL: 'CTRL', ESCAPE: 'ESC', RETURN: 'ENTER', DEL: 'DELETE', INS: 'INSERT',
|
|
71
|
+
PGUP: 'PAGEUP', PGDN: 'PAGEDOWN', WIN: 'META', WINDOWS: 'META', SUPER: 'META',
|
|
72
|
+
SPACEBAR: 'SPACE', CAPS: 'CAPSLOCK', OPTION: 'ALT',
|
|
73
|
+
};
|
|
74
|
+
/** 按控制器 **type** 取表(不是 name)。没有表的类型返回 null——调用方要告警,不能退回 Win32 猜。 */
|
|
75
|
+
export function hotkeyTableFor(controllerType, useWin32VkCode = false) {
|
|
76
|
+
switch (controllerType) {
|
|
77
|
+
case 'Win32':
|
|
78
|
+
return WIN32;
|
|
79
|
+
case 'Adb':
|
|
80
|
+
return ADB;
|
|
81
|
+
case 'MacOS':
|
|
82
|
+
return MACOS;
|
|
83
|
+
case 'WlRoots':
|
|
84
|
+
return WLROOTS;
|
|
85
|
+
/* Linux 控制器可声明 `linux.use_win32_vk_code`:true 时服务端按 Win32 VK 解释,false 用 evdev */
|
|
86
|
+
case 'Linux':
|
|
87
|
+
return useWin32VkCode ? WIN32 : WLROOTS;
|
|
88
|
+
default:
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function normalizeKey(name) {
|
|
93
|
+
const up = name.trim().toUpperCase();
|
|
94
|
+
return ALIASES[up] ?? up;
|
|
95
|
+
}
|
|
96
|
+
/** "Ctrl+Shift+A" → { primary: "A", modifiers: ["Ctrl","Shift"] };末段为主键。 */
|
|
97
|
+
export function splitHotkeyCombo(value) {
|
|
98
|
+
const parts = value.split('+').map((p) => p.trim()).filter(Boolean);
|
|
99
|
+
if (!parts.length)
|
|
100
|
+
return { primary: '', modifiers: [] };
|
|
101
|
+
return { primary: parts[parts.length - 1], modifiers: parts.slice(0, -1) };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 把一个 hotkey 字段解析成四种占位符的取值。
|
|
105
|
+
*
|
|
106
|
+
* 缺修饰键不是错(单键 "E" 本来就没有修饰键),补 0 = 无按键;**主键查不到是错**——
|
|
107
|
+
* 写个 0 进去等于"按了空键",任务会静默点不动。
|
|
108
|
+
*/
|
|
109
|
+
export function resolveHotkeyTokens(fieldName, raw, table) {
|
|
110
|
+
const { primary, modifiers } = splitHotkeyCombo(raw);
|
|
111
|
+
if (!primary)
|
|
112
|
+
return { tokens: {}, error: '取值为空' };
|
|
113
|
+
const code = table[normalizeKey(primary)];
|
|
114
|
+
if (code === undefined) {
|
|
115
|
+
return { tokens: {}, error: '主键 ' + primary + ' 不在该控制器的键码表里(' + raw + ')' };
|
|
116
|
+
}
|
|
117
|
+
const mod = (name) => {
|
|
118
|
+
if (!name)
|
|
119
|
+
return 0;
|
|
120
|
+
const c = table[normalizeKey(name)];
|
|
121
|
+
/* 修饰键查不到也按"无"处理会让组合键变成单键——那是静默错,直接报错 */
|
|
122
|
+
if (c === undefined)
|
|
123
|
+
throw new Error('修饰键 ' + name + ' 不在该控制器的键码表里(' + raw + ')');
|
|
124
|
+
return c;
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
const m1 = mod(modifiers[0]);
|
|
128
|
+
const m2 = mod(modifiers[1]);
|
|
129
|
+
return {
|
|
130
|
+
tokens: {
|
|
131
|
+
[fieldName]: code,
|
|
132
|
+
[fieldName + '.primary']: code,
|
|
133
|
+
[fieldName + '.modifier1']: m1,
|
|
134
|
+
[fieldName + '.modifier2']: m2,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
return { tokens: {}, error: String(e.message || e) };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=hotkey.js.map
|
package/lib/interface/index.js
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
export { parseJsonc } from './jsonc.js';
|
|
9
9
|
export { loadInterface, findInterfaceFile, } from './load.js';
|
|
10
10
|
export { planController, resolveResourcePaths, } from './plan.js';
|
|
11
|
-
export { computePipelineOverride,
|
|
11
|
+
export { computePipelineOverride, appendOverride } from './override.js';
|
|
12
12
|
//# sourceMappingURL=index.js.map
|
package/lib/interface/load.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 载入项目 interface.json(ProjectInterface v2)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (run --project
|
|
4
|
+
* 解析面就是本工具真正消费的那部分:控制器(连接规划)、资源(路径解析)、任务入口
|
|
5
|
+
* (run --project)、option / preset / global_option(pipeline_override 四级链)、
|
|
6
|
+
* agent 与 pretask(子进程相关)、import(v2.2.0 合并规则)。
|
|
7
|
+
*
|
|
8
|
+
* 明确**不**解析、不消费的:task 级 pipeline_override(本工具只合成 option 链,任务级写死的
|
|
9
|
+
* 覆盖要由 Client 应用)、i18n(languages 与 `$` 键——当前不输出人类标签)、group / setting /
|
|
10
|
+
* telemetry、resource.hash(v2.6.0 的一致性校验,未实现)。
|
|
6
11
|
* 全过程不抛异常:问题进 problems[],由调用方决定怎么呈现。
|
|
7
12
|
*/
|
|
8
13
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -41,6 +46,7 @@ export function loadInterface(dir) {
|
|
|
41
46
|
file,
|
|
42
47
|
dir: file ? dirname(file) : null,
|
|
43
48
|
version: null,
|
|
49
|
+
projectVersion: null,
|
|
44
50
|
name: null,
|
|
45
51
|
controllers: [],
|
|
46
52
|
resources: [],
|
|
@@ -97,10 +103,20 @@ export function loadInterface(dir) {
|
|
|
97
103
|
}
|
|
98
104
|
const w = obj(c.win32);
|
|
99
105
|
const g = obj(c.gamepad);
|
|
106
|
+
const mo = obj(c.macos);
|
|
107
|
+
const linuxDecl = obj(c.linux);
|
|
100
108
|
controllers.push({
|
|
101
109
|
name,
|
|
102
110
|
type,
|
|
111
|
+
raw: c,
|
|
103
112
|
display: Object.keys(display).length ? display : undefined,
|
|
113
|
+
macos: mo
|
|
114
|
+
? {
|
|
115
|
+
titleRegex: str(mo.title_regex),
|
|
116
|
+
screencap: str(mo.screencap),
|
|
117
|
+
input: str(mo.input),
|
|
118
|
+
}
|
|
119
|
+
: undefined,
|
|
104
120
|
win32: w
|
|
105
121
|
? {
|
|
106
122
|
classRegex: str(w.class_regex),
|
|
@@ -120,6 +136,17 @@ export function loadInterface(dir) {
|
|
|
120
136
|
: undefined,
|
|
121
137
|
attachResourcePath: strList(c.attach_resource_path),
|
|
122
138
|
option: strList(c.option),
|
|
139
|
+
...(linuxDecl
|
|
140
|
+
? {
|
|
141
|
+
linux: {
|
|
142
|
+
...(linuxDecl.use_win32_vk_code === true ? { useWin32VkCode: true } : {}),
|
|
143
|
+
...(str(linuxDecl.screencap) ? { screencap: str(linuxDecl.screencap) } : {}),
|
|
144
|
+
...(str(linuxDecl.input) ? { input: str(linuxDecl.input) } : {}),
|
|
145
|
+
...(str(linuxDecl.pipewire_source) ? { pipewireSource: str(linuxDecl.pipewire_source) } : {}),
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
: {}),
|
|
149
|
+
...(c.permission_required === true ? { permissionRequired: true } : {}),
|
|
123
150
|
});
|
|
124
151
|
}
|
|
125
152
|
if (!controllers.length)
|
|
@@ -133,7 +160,10 @@ export function loadInterface(dir) {
|
|
|
133
160
|
problems.push({ message: 'resource 项缺少 name 或 path,已跳过' });
|
|
134
161
|
continue;
|
|
135
162
|
}
|
|
136
|
-
resources.push({
|
|
163
|
+
resources.push({
|
|
164
|
+
name, paths, controllers: strList(r.controller), option: strList(r.option),
|
|
165
|
+
...(r.hash !== undefined ? { hash: str(r.hash) } : {}), raw: r,
|
|
166
|
+
});
|
|
137
167
|
}
|
|
138
168
|
const tasks = [];
|
|
139
169
|
const options = {};
|
|
@@ -147,7 +177,7 @@ export function loadInterface(dir) {
|
|
|
147
177
|
const exec = a ? str(a.child_exec) : undefined;
|
|
148
178
|
if (!a || !exec)
|
|
149
179
|
continue;
|
|
150
|
-
agents.push({ exec, args: strList(a.child_args) });
|
|
180
|
+
agents.push({ exec, args: strList(a.child_args), ...(str(a.identifier) !== undefined ? { identifier: str(a.identifier) } : {}) });
|
|
151
181
|
}
|
|
152
182
|
};
|
|
153
183
|
const readTasks = (list, src) => {
|
|
@@ -158,12 +188,14 @@ export function loadInterface(dir) {
|
|
|
158
188
|
problems.push({ message: 'task 项缺少 name,已跳过(' + src + ')' });
|
|
159
189
|
continue;
|
|
160
190
|
}
|
|
191
|
+
const po = obj(t.pipeline_override);
|
|
161
192
|
tasks.push({
|
|
162
193
|
name,
|
|
163
194
|
entry: str(t.entry),
|
|
164
195
|
controller: strList(t.controller),
|
|
165
196
|
resource: strList(t.resource),
|
|
166
197
|
option: strList(t.option),
|
|
198
|
+
...(po ? { pipelineOverride: po } : {}),
|
|
167
199
|
});
|
|
168
200
|
}
|
|
169
201
|
};
|
|
@@ -176,7 +208,10 @@ export function loadInterface(dir) {
|
|
|
176
208
|
}
|
|
177
209
|
for (const [key, val] of Object.entries(d)) {
|
|
178
210
|
const v = obj(val);
|
|
179
|
-
|
|
211
|
+
/* type 缺省按 select(上游 PI 文档与 Types.h:212 的默认);仍记 problem,
|
|
212
|
+
* 因为它大概率是漏写而非有意缺省——警告可见,行为对齐上游。 */
|
|
213
|
+
const type = v ? (str(v.type) ?? 'select') : undefined;
|
|
214
|
+
if (!v || !type) {
|
|
180
215
|
problems.push({ message: 'option ' + key + ' 缺少 type,已跳过(' + src + ')' });
|
|
181
216
|
continue;
|
|
182
217
|
}
|
|
@@ -193,13 +228,55 @@ export function loadInterface(dir) {
|
|
|
193
228
|
option: strList(c2.option),
|
|
194
229
|
});
|
|
195
230
|
}
|
|
231
|
+
const tpl = obj(v.pipeline_override);
|
|
232
|
+
const inputs = [];
|
|
233
|
+
for (const it of Array.isArray(v.inputs) ? v.inputs : []) {
|
|
234
|
+
const f = obj(it);
|
|
235
|
+
const fname = f ? str(f.name) : undefined;
|
|
236
|
+
if (!f || !fname)
|
|
237
|
+
continue;
|
|
238
|
+
const pwd = f.password === true;
|
|
239
|
+
inputs.push({
|
|
240
|
+
name: fname,
|
|
241
|
+
...(str(f.default) !== undefined && !pwd ? { default: str(f.default) } : {}),
|
|
242
|
+
...(str(f.pipeline_type) !== undefined ? { pipelineType: str(f.pipeline_type) } : {}),
|
|
243
|
+
...(str(f.verify) !== undefined ? { verify: str(f.verify) } : {}),
|
|
244
|
+
...(pwd ? { password: true } : {}),
|
|
245
|
+
});
|
|
246
|
+
if (pwd && str(f.default) !== undefined) {
|
|
247
|
+
problems.push({ message: 'option ' + key + ' 的输入字段 ' + fname + ':password=true 时规范禁止同时设 default,default 已忽略' });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const hotkeys = [];
|
|
251
|
+
for (const it of Array.isArray(v.hotkeys) ? v.hotkeys : []) {
|
|
252
|
+
const f = obj(it);
|
|
253
|
+
const fname = f ? str(f.name) : undefined;
|
|
254
|
+
if (!f || !fname)
|
|
255
|
+
continue;
|
|
256
|
+
hotkeys.push({ name: fname, ...(str(f.default) !== undefined ? { default: str(f.default) } : {}) });
|
|
257
|
+
}
|
|
258
|
+
const minC = num(v.min_count);
|
|
259
|
+
const maxC = num(v.max_count);
|
|
260
|
+
if (type === 'checkbox') {
|
|
261
|
+
if (minC !== undefined && maxC !== undefined && minC > maxC) {
|
|
262
|
+
problems.push({ message: 'option ' + key + ':min_count=' + minC + ' > max_count=' + maxC + ',约束自相矛盾' });
|
|
263
|
+
}
|
|
264
|
+
if (minC !== undefined && cases.length && minC > cases.length) {
|
|
265
|
+
problems.push({ message: 'option ' + key + ':min_count=' + minC + ' 超过声明的 case 数 ' + cases.length + ',永远选不够' });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
196
268
|
options[key] = {
|
|
197
269
|
name: key,
|
|
198
|
-
type
|
|
270
|
+
type,
|
|
199
271
|
cases,
|
|
272
|
+
...(tpl ? { pipelineOverride: tpl } : {}),
|
|
273
|
+
...(inputs.length ? { inputs } : {}),
|
|
274
|
+
...(hotkeys.length ? { hotkeys } : {}),
|
|
200
275
|
...(v.default_case !== undefined ? { defaultCase: v.default_case } : {}),
|
|
201
276
|
controller: strList(v.controller),
|
|
202
277
|
resource: strList(v.resource),
|
|
278
|
+
...(type === 'checkbox' && minC !== undefined ? { minCount: minC } : {}),
|
|
279
|
+
...(type === 'checkbox' && maxC !== undefined ? { maxCount: maxC } : {}),
|
|
203
280
|
};
|
|
204
281
|
}
|
|
205
282
|
};
|
|
@@ -228,12 +305,28 @@ export function loadInterface(dir) {
|
|
|
228
305
|
for (const g of strList(o.global_option))
|
|
229
306
|
if (!globalOption.includes(g))
|
|
230
307
|
globalOption.push(g);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
308
|
+
/* pretask:`exec` 必填;`args` / `option` / 过滤列表缺省为空。单条也可写成对象而不是数组。 */
|
|
309
|
+
const readPretasks = (list, src) => {
|
|
310
|
+
for (const pt of Array.isArray(list) ? list : (list !== undefined ? [list] : [])) {
|
|
311
|
+
const p2 = obj(pt);
|
|
312
|
+
const exec = p2 ? str(p2.exec) : undefined;
|
|
313
|
+
if (!p2)
|
|
314
|
+
continue;
|
|
315
|
+
if (!exec) {
|
|
316
|
+
problems.push({ message: 'pretask 缺少 exec,已跳过(' + src + ')' });
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
pretask.push({
|
|
320
|
+
...(str(p2.name) !== undefined ? { name: str(p2.name) } : {}),
|
|
321
|
+
exec,
|
|
322
|
+
args: strList(p2.args),
|
|
323
|
+
option: strList(p2.option),
|
|
324
|
+
...(strList(p2.controller).length ? { controller: strList(p2.controller) } : {}),
|
|
325
|
+
...(strList(p2.resource).length ? { resource: strList(p2.resource) } : {}),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
readPretasks(o.pretask, '主文件');
|
|
237
330
|
/* import 合并(v2.2.0):只合 task/option/preset/global_option/pretask(及 group/setting,本工具不消费)。
|
|
238
331
|
* controller/resource 不可导入——子文件声明了也忽略并记 problems。循环导入用 visited 集合挡住。
|
|
239
332
|
*
|
|
@@ -283,15 +376,12 @@ export function loadInterface(dir) {
|
|
|
283
376
|
for (const g of strList(child.global_option))
|
|
284
377
|
if (!globalOption.includes(g))
|
|
285
378
|
globalOption.push(g);
|
|
286
|
-
|
|
287
|
-
const p2 = obj(pt);
|
|
288
|
-
if (p2)
|
|
289
|
-
pretask.push({ name: str(p2.name), exec: str(p2.exec) });
|
|
290
|
-
}
|
|
379
|
+
readPretasks(child.pretask, rel);
|
|
291
380
|
}
|
|
292
381
|
return {
|
|
293
382
|
...base,
|
|
294
383
|
version: version ?? null,
|
|
384
|
+
projectVersion: str(o.version) ?? null,
|
|
295
385
|
name: str(o.name) ?? null,
|
|
296
386
|
controllers,
|
|
297
387
|
resources,
|