@web-auto/webauto 0.1.14 → 0.1.16
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 +137 -0
- package/apps/desktop-console/dist/renderer/index.js +63 -17
- package/apps/webauto/entry/flow-gate.mjs +139 -0
- package/apps/webauto/entry/lib/flow-gate.mjs +466 -0
- package/apps/webauto/entry/xhs-unified.mjs +109 -5
- package/bin/webauto.mjs +15 -2
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +64 -44
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +31 -11
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +83 -24
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +69 -8
- package/package.json +2 -1
package/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# @web-auto/webauto
|
|
2
|
+
|
|
3
|
+
Windows 优先的 WebAuto CLI + Desktop UI 使用说明。
|
|
4
|
+
|
|
5
|
+
## 1. 安装(Windows)
|
|
6
|
+
|
|
7
|
+
要求:
|
|
8
|
+
- Node.js 18+(建议 20+)
|
|
9
|
+
- npm 可用
|
|
10
|
+
|
|
11
|
+
全局安装:
|
|
12
|
+
|
|
13
|
+
```bat
|
|
14
|
+
npm install -g @web-auto/webauto
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
验证:
|
|
18
|
+
|
|
19
|
+
```bat
|
|
20
|
+
webauto --help
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 2. 直接启动 UI
|
|
24
|
+
|
|
25
|
+
最直接方式:
|
|
26
|
+
|
|
27
|
+
```bat
|
|
28
|
+
webauto ui console
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
或用 UI CLI 自动拉起(适合脚本化):
|
|
32
|
+
|
|
33
|
+
```bat
|
|
34
|
+
webauto ui cli start --json
|
|
35
|
+
webauto ui cli status --json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
说明:
|
|
39
|
+
- 首次启动会自动准备 Desktop runtime 依赖(Electron),不需要手工安装。
|
|
40
|
+
- 启动成功后,UI CLI bridge 默认端口是 `7716`。
|
|
41
|
+
|
|
42
|
+
## 3. UI CLI 常用命令(模拟真实 UI 操作)
|
|
43
|
+
|
|
44
|
+
```bat
|
|
45
|
+
webauto ui cli --help
|
|
46
|
+
|
|
47
|
+
:: 切换到任务页
|
|
48
|
+
webauto ui cli tab --tab tasks --json
|
|
49
|
+
|
|
50
|
+
:: 输入任务参数
|
|
51
|
+
webauto ui cli input --selector "#task-keyword" --value "deepseek" --json
|
|
52
|
+
webauto ui cli input --selector "#task-target" --value "20" --json
|
|
53
|
+
|
|
54
|
+
:: 点击执行
|
|
55
|
+
webauto ui cli click --selector "#task-run-ephemeral-btn" --json
|
|
56
|
+
|
|
57
|
+
:: 读取当前 UI 快照
|
|
58
|
+
webauto ui cli status --json
|
|
59
|
+
webauto ui cli snapshot --json
|
|
60
|
+
|
|
61
|
+
:: 元素探测
|
|
62
|
+
webauto ui cli probe --selector "#task-likes" --json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
任务状态查看(后端状态):
|
|
66
|
+
|
|
67
|
+
```bat
|
|
68
|
+
webauto xhs status --json
|
|
69
|
+
webauto xhs status --run-id <runId> --json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 4. Windows 路径与设置
|
|
73
|
+
|
|
74
|
+
默认数据目录(未设置环境变量时):
|
|
75
|
+
- 有 `D:` 盘:`D:\webauto`
|
|
76
|
+
- 无 `D:` 盘:`%USERPROFILE%\.webauto`
|
|
77
|
+
|
|
78
|
+
可选环境变量:
|
|
79
|
+
- `WEBAUTO_HOME`:显式指定数据根目录(推荐)
|
|
80
|
+
- `WEBAUTO_ROOT` / `WEBAUTO_PORTABLE_ROOT`:兼容旧变量(会归一化到 `.webauto`)
|
|
81
|
+
|
|
82
|
+
PowerShell 设置示例:
|
|
83
|
+
|
|
84
|
+
```powershell
|
|
85
|
+
$env:WEBAUTO_HOME = "D:\webauto"
|
|
86
|
+
webauto ui console
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
CMD 设置示例:
|
|
90
|
+
|
|
91
|
+
```bat
|
|
92
|
+
set WEBAUTO_HOME=D:\webauto
|
|
93
|
+
webauto ui console
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
不设置也可以直接用,系统会按默认规则落盘。
|
|
97
|
+
|
|
98
|
+
## 5. 首次安装常见问题
|
|
99
|
+
|
|
100
|
+
### 5.1 `Lock file can not be created` / 启动失败
|
|
101
|
+
|
|
102
|
+
说明:通常是残留的 Electron/Node 进程占用。
|
|
103
|
+
|
|
104
|
+
```bat
|
|
105
|
+
taskkill /F /IM electron.exe /T
|
|
106
|
+
taskkill /F /IM node.exe /T
|
|
107
|
+
webauto ui console
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 5.2 `ui cli fetch failed`
|
|
111
|
+
|
|
112
|
+
先确认 UI 已启动并就绪:
|
|
113
|
+
|
|
114
|
+
```bat
|
|
115
|
+
webauto ui cli start --json
|
|
116
|
+
webauto ui cli status --json
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
若仍失败,前台启动看日志:
|
|
120
|
+
|
|
121
|
+
```bat
|
|
122
|
+
webauto ui console --no-daemon
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 6. 资源检查/安装
|
|
126
|
+
|
|
127
|
+
```bat
|
|
128
|
+
webauto xhs install --check --json
|
|
129
|
+
webauto xhs install --download-browser --json
|
|
130
|
+
webauto xhs install --download-geoip --json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 7. 开发者(仓库模式)
|
|
134
|
+
|
|
135
|
+
在仓库中开发请看:
|
|
136
|
+
- `apps/desktop-console/README.md`
|
|
137
|
+
|
|
@@ -2870,10 +2870,6 @@ function renderTasksPanel(root, ctx2) {
|
|
|
2870
2870
|
}
|
|
2871
2871
|
async function saveTask(runImmediately = false) {
|
|
2872
2872
|
const data = collectFormData();
|
|
2873
|
-
if (runImmediately && data.scheduleMode === "immediate") {
|
|
2874
|
-
await runWithoutSave();
|
|
2875
|
-
return;
|
|
2876
|
-
}
|
|
2877
2873
|
saveBtn.disabled = true;
|
|
2878
2874
|
runBtn.disabled = true;
|
|
2879
2875
|
runEphemeralBtn.disabled = true;
|
|
@@ -3212,6 +3208,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3212
3208
|
let stoppedAt = null;
|
|
3213
3209
|
let elapsedTimer = null;
|
|
3214
3210
|
let statePollTimer = null;
|
|
3211
|
+
let accountLabelPollTimer = null;
|
|
3215
3212
|
let unsubscribeState = null;
|
|
3216
3213
|
let unsubscribeCmd = null;
|
|
3217
3214
|
const contextRun = ctx2?.xhsCurrentRun && typeof ctx2.xhsCurrentRun === "object" ? ctx2.xhsCurrentRun : null;
|
|
@@ -3225,6 +3222,10 @@ function renderDashboard(root, ctx2) {
|
|
|
3225
3222
|
const maxLogs = 500;
|
|
3226
3223
|
const maxRecentErrors = 8;
|
|
3227
3224
|
const maxLikedLinks = 30;
|
|
3225
|
+
const accountLabelByProfile = /* @__PURE__ */ new Map();
|
|
3226
|
+
let accountLabelRefreshInFlight = false;
|
|
3227
|
+
let accountLabelRefreshedAt = 0;
|
|
3228
|
+
const accountLabelRefreshTtlMs = 15e3;
|
|
3228
3229
|
const initialTaskId = String(contextRun?.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3229
3230
|
if (initialTaskId) {
|
|
3230
3231
|
taskConfigId.textContent = initialTaskId;
|
|
@@ -3241,14 +3242,49 @@ function renderDashboard(root, ctx2) {
|
|
|
3241
3242
|
const text = String(value ?? "").trim();
|
|
3242
3243
|
return text.length > 0 && text !== "-";
|
|
3243
3244
|
};
|
|
3245
|
+
const resolveAccountLabel = (profileIdLike) => {
|
|
3246
|
+
const profileId = String(profileIdLike || "").trim();
|
|
3247
|
+
if (!profileId) return "-";
|
|
3248
|
+
return accountLabelByProfile.get(profileId) || profileId;
|
|
3249
|
+
};
|
|
3250
|
+
const applyAccountLabel = (profileIdLike) => {
|
|
3251
|
+
const profileId = String(profileIdLike || "").trim();
|
|
3252
|
+
if (!profileId) return;
|
|
3253
|
+
activeProfileId = profileId;
|
|
3254
|
+
taskAccount.textContent = resolveAccountLabel(profileId);
|
|
3255
|
+
if (!accountLabelByProfile.has(profileId)) {
|
|
3256
|
+
void refreshAccountLabels(true);
|
|
3257
|
+
}
|
|
3258
|
+
};
|
|
3259
|
+
async function refreshAccountLabels(force = false) {
|
|
3260
|
+
if (accountLabelRefreshInFlight) return;
|
|
3261
|
+
if (!force && Date.now() - accountLabelRefreshedAt < accountLabelRefreshTtlMs) return;
|
|
3262
|
+
if (typeof ctx2.api?.cmdRunJson !== "function") return;
|
|
3263
|
+
if (typeof ctx2.api?.pathJoin !== "function") return;
|
|
3264
|
+
accountLabelRefreshInFlight = true;
|
|
3265
|
+
try {
|
|
3266
|
+
const rows = await listAccountProfiles(ctx2.api, { platform: "xiaohongshu" });
|
|
3267
|
+
accountLabelByProfile.clear();
|
|
3268
|
+
for (const row of rows) {
|
|
3269
|
+
const profileId = String(row?.profileId || "").trim();
|
|
3270
|
+
if (!profileId) continue;
|
|
3271
|
+
const label = String(row?.alias || row?.name || profileId).trim() || profileId;
|
|
3272
|
+
accountLabelByProfile.set(profileId, label);
|
|
3273
|
+
}
|
|
3274
|
+
accountLabelRefreshedAt = Date.now();
|
|
3275
|
+
if (activeProfileId) {
|
|
3276
|
+
taskAccount.textContent = resolveAccountLabel(activeProfileId);
|
|
3277
|
+
}
|
|
3278
|
+
} catch {
|
|
3279
|
+
} finally {
|
|
3280
|
+
accountLabelRefreshInFlight = false;
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3244
3283
|
if (contextRun) {
|
|
3245
3284
|
if (hasRenderableValue(contextRun.keyword)) taskKeyword.textContent = String(contextRun.keyword);
|
|
3246
3285
|
if (Number(contextRun.target) > 0) taskTarget.textContent = String(Number(contextRun.target));
|
|
3247
3286
|
if (hasRenderableValue(contextRun.profileId)) {
|
|
3248
|
-
|
|
3249
|
-
const profileId = String(contextRun.profileId);
|
|
3250
|
-
taskAccount.textContent = aliases[profileId] || profileId;
|
|
3251
|
-
activeProfileId = profileId;
|
|
3287
|
+
applyAccountLabel(contextRun.profileId);
|
|
3252
3288
|
}
|
|
3253
3289
|
if (hasRenderableValue(contextRun.taskId)) taskConfigId.textContent = String(contextRun.taskId);
|
|
3254
3290
|
const startedAtTs = Date.parse(String(contextRun.startedAt || ""));
|
|
@@ -3454,6 +3490,18 @@ function renderDashboard(root, ctx2) {
|
|
|
3454
3490
|
clearInterval(statePollTimer);
|
|
3455
3491
|
statePollTimer = null;
|
|
3456
3492
|
}
|
|
3493
|
+
function startAccountLabelPoll() {
|
|
3494
|
+
if (accountLabelPollTimer) return;
|
|
3495
|
+
accountLabelPollTimer = setInterval(() => {
|
|
3496
|
+
if (paused) return;
|
|
3497
|
+
void refreshAccountLabels(false);
|
|
3498
|
+
}, 3e4);
|
|
3499
|
+
}
|
|
3500
|
+
function stopAccountLabelPoll() {
|
|
3501
|
+
if (!accountLabelPollTimer) return;
|
|
3502
|
+
clearInterval(accountLabelPollTimer);
|
|
3503
|
+
accountLabelPollTimer = null;
|
|
3504
|
+
}
|
|
3457
3505
|
function addLog(line, type = "info") {
|
|
3458
3506
|
const ts = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
|
|
3459
3507
|
const logLine = createEl("div", { className: "log-line" });
|
|
@@ -3549,9 +3597,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3549
3597
|
taskTarget.textContent = String(state.target);
|
|
3550
3598
|
}
|
|
3551
3599
|
if (state.profileId) {
|
|
3552
|
-
|
|
3553
|
-
taskAccount.textContent = aliases[state.profileId] || state.profileId;
|
|
3554
|
-
activeProfileId = String(state.profileId || "").trim();
|
|
3600
|
+
applyAccountLabel(state.profileId);
|
|
3555
3601
|
}
|
|
3556
3602
|
const taskId = String(state.taskId || state.scheduleTaskId || state.configTaskId || "").trim();
|
|
3557
3603
|
if (taskId) {
|
|
@@ -3636,7 +3682,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3636
3682
|
resetDashboardForNewRun("\u65B0\u4EFB\u52A1\u542F\u52A8", ts);
|
|
3637
3683
|
if (payload.keyword) taskKeyword.textContent = String(payload.keyword);
|
|
3638
3684
|
if (payload.maxNotes) taskTarget.textContent = String(payload.maxNotes);
|
|
3639
|
-
if (payload.profileId)
|
|
3685
|
+
if (payload.profileId) applyAccountLabel(payload.profileId);
|
|
3640
3686
|
if (payload.taskId) {
|
|
3641
3687
|
const taskId = String(payload.taskId || "").trim();
|
|
3642
3688
|
if (taskId) {
|
|
@@ -3788,9 +3834,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3788
3834
|
if (summary.keyword) taskKeyword.textContent = String(summary.keyword);
|
|
3789
3835
|
if (assigned) taskTarget.textContent = String(assigned);
|
|
3790
3836
|
if (profile?.profileId) {
|
|
3791
|
-
|
|
3792
|
-
taskAccount.textContent = aliases[profile.profileId] || profile.profileId;
|
|
3793
|
-
activeProfileId = String(profile.profileId || "").trim();
|
|
3837
|
+
applyAccountLabel(profile.profileId);
|
|
3794
3838
|
}
|
|
3795
3839
|
const runId = String(profile?.runId || summary?.runId || "").trim();
|
|
3796
3840
|
if (runId) {
|
|
@@ -3930,8 +3974,7 @@ function renderDashboard(root, ctx2) {
|
|
|
3930
3974
|
taskTarget.textContent = String(config.target || 50);
|
|
3931
3975
|
}
|
|
3932
3976
|
if (!hasRenderableValue(contextRun?.profileId) && config.lastProfileId) {
|
|
3933
|
-
|
|
3934
|
-
taskAccount.textContent = aliases[config.lastProfileId] || config.lastProfileId;
|
|
3977
|
+
applyAccountLabel(config.lastProfileId);
|
|
3935
3978
|
}
|
|
3936
3979
|
const taskId = String(contextRun?.taskId || config.taskId || ctx2?.activeTaskConfigId || "").trim();
|
|
3937
3980
|
if (taskId) {
|
|
@@ -4008,14 +4051,17 @@ function renderDashboard(root, ctx2) {
|
|
|
4008
4051
|
}
|
|
4009
4052
|
};
|
|
4010
4053
|
renderRunSummary();
|
|
4054
|
+
void refreshAccountLabels(true);
|
|
4011
4055
|
loadTaskInfo();
|
|
4012
4056
|
subscribeToUpdates();
|
|
4013
4057
|
fetchCurrentState();
|
|
4014
4058
|
startStatePoll();
|
|
4059
|
+
startAccountLabelPoll();
|
|
4015
4060
|
startElapsedTimer();
|
|
4016
4061
|
return () => {
|
|
4017
4062
|
stopElapsedTimer();
|
|
4018
4063
|
stopStatePoll();
|
|
4064
|
+
stopAccountLabelPoll();
|
|
4019
4065
|
if (unsubscribeState) unsubscribeState();
|
|
4020
4066
|
if (unsubscribeCmd) unsubscribeCmd();
|
|
4021
4067
|
if (unsubscribeBus) unsubscribeBus();
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import minimist from 'minimist';
|
|
3
|
+
import {
|
|
4
|
+
listPlatformFlowGates,
|
|
5
|
+
loadFlowGateDoc,
|
|
6
|
+
patchPlatformFlowGate,
|
|
7
|
+
resetPlatformFlowGate,
|
|
8
|
+
resolveFlowGatePath,
|
|
9
|
+
resolvePlatformFlowGate,
|
|
10
|
+
} from './lib/flow-gate.mjs';
|
|
11
|
+
|
|
12
|
+
function normalizePlatform(value) {
|
|
13
|
+
const text = String(value || '').trim().toLowerCase();
|
|
14
|
+
if (!text) return 'xiaohongshu';
|
|
15
|
+
if (text === 'xhs') return 'xiaohongshu';
|
|
16
|
+
return text;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toJsonMode(argv) {
|
|
20
|
+
return argv.json === true || argv.j === true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function printHelp() {
|
|
24
|
+
console.log([
|
|
25
|
+
'Usage: node apps/webauto/entry/flow-gate.mjs <get|list|set|reset|path> [options]',
|
|
26
|
+
'Options:',
|
|
27
|
+
' --platform <name> 平台名(默认 xiaohongshu)',
|
|
28
|
+
' --patch-json <json> set 动作补丁 JSON(对象)',
|
|
29
|
+
' --json 输出 JSON',
|
|
30
|
+
'',
|
|
31
|
+
'Examples:',
|
|
32
|
+
' node apps/webauto/entry/flow-gate.mjs get --platform xiaohongshu --json',
|
|
33
|
+
' node apps/webauto/entry/flow-gate.mjs list --json',
|
|
34
|
+
' node apps/webauto/entry/flow-gate.mjs set --platform xiaohongshu --patch-json \'{"noteInterval":{"minMs":2600,"maxMs":5200}}\' --json',
|
|
35
|
+
' node apps/webauto/entry/flow-gate.mjs reset --platform xiaohongshu --json',
|
|
36
|
+
].join('\n'));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parsePatchJson(raw) {
|
|
40
|
+
const text = String(raw || '').trim();
|
|
41
|
+
if (!text) throw new Error('missing --patch-json');
|
|
42
|
+
let parsed = null;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(text);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error(`invalid --patch-json: ${error?.message || String(error)}`);
|
|
47
|
+
}
|
|
48
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
49
|
+
throw new Error('--patch-json must be object JSON');
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function main() {
|
|
55
|
+
const argv = minimist(process.argv.slice(2), {
|
|
56
|
+
boolean: ['help', 'h', 'json', 'j'],
|
|
57
|
+
string: ['platform', 'patch-json'],
|
|
58
|
+
});
|
|
59
|
+
if (argv.help || argv.h) {
|
|
60
|
+
printHelp();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const action = String(argv._[0] || 'get').trim().toLowerCase();
|
|
65
|
+
const platform = normalizePlatform(argv.platform);
|
|
66
|
+
const jsonMode = toJsonMode(argv);
|
|
67
|
+
const path = resolveFlowGatePath();
|
|
68
|
+
|
|
69
|
+
if (action === 'path') {
|
|
70
|
+
if (jsonMode) {
|
|
71
|
+
console.log(JSON.stringify({ ok: true, path }, null, 2));
|
|
72
|
+
} else {
|
|
73
|
+
console.log(path);
|
|
74
|
+
}
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (action === 'get') {
|
|
79
|
+
const gate = await resolvePlatformFlowGate(platform);
|
|
80
|
+
if (jsonMode) {
|
|
81
|
+
console.log(JSON.stringify({ ok: true, platform, path, gate }, null, 2));
|
|
82
|
+
} else {
|
|
83
|
+
console.log(`[flow-gate] platform=${platform}`);
|
|
84
|
+
console.log(JSON.stringify(gate, null, 2));
|
|
85
|
+
console.log(`[flow-gate] file=${path}`);
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (action === 'list') {
|
|
91
|
+
const gates = await listPlatformFlowGates();
|
|
92
|
+
if (jsonMode) {
|
|
93
|
+
console.log(JSON.stringify({ ok: true, path, platforms: gates }, null, 2));
|
|
94
|
+
} else {
|
|
95
|
+
console.log(JSON.stringify(gates, null, 2));
|
|
96
|
+
console.log(`[flow-gate] file=${path}`);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (action === 'set') {
|
|
102
|
+
const patch = parsePatchJson(argv['patch-json']);
|
|
103
|
+
const gate = await patchPlatformFlowGate(platform, patch);
|
|
104
|
+
if (jsonMode) {
|
|
105
|
+
console.log(JSON.stringify({ ok: true, action, platform, path, gate }, null, 2));
|
|
106
|
+
} else {
|
|
107
|
+
console.log(`[flow-gate] updated platform=${platform}`);
|
|
108
|
+
console.log(JSON.stringify(gate, null, 2));
|
|
109
|
+
console.log(`[flow-gate] file=${path}`);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (action === 'reset') {
|
|
115
|
+
const gate = await resetPlatformFlowGate(platform);
|
|
116
|
+
if (jsonMode) {
|
|
117
|
+
console.log(JSON.stringify({ ok: true, action, platform, path, gate }, null, 2));
|
|
118
|
+
} else {
|
|
119
|
+
console.log(`[flow-gate] reset platform=${platform}`);
|
|
120
|
+
console.log(JSON.stringify(gate, null, 2));
|
|
121
|
+
console.log(`[flow-gate] file=${path}`);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (action === 'show-doc') {
|
|
127
|
+
const doc = await loadFlowGateDoc();
|
|
128
|
+
if (jsonMode) console.log(JSON.stringify({ ok: true, path, doc }, null, 2));
|
|
129
|
+
else console.log(JSON.stringify(doc, null, 2));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
throw new Error(`unknown action: ${action}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main().catch((err) => {
|
|
137
|
+
console.error(err?.message || String(err));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
});
|