@windsland52/maafw-live 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +293 -2
- package/bin/maafw-live.mjs +25 -0
- package/docs/daemon-protocol.md +121 -0
- package/docs/keyframe-retention-contract-v0.md +225 -0
- package/docs/session-unify-v0.md +115 -0
- package/lib/client/daemon.js +276 -0
- package/lib/commands/annotate.js +179 -0
- package/lib/commands/calibrate.js +61 -0
- package/lib/commands/color.js +73 -0
- package/lib/commands/crop.js +228 -0
- package/lib/commands/dispatch.js +121 -0
- package/lib/commands/env.js +409 -0
- package/lib/commands/index.js +70 -0
- package/lib/commands/input.js +257 -0
- package/lib/commands/kf.js +167 -0
- package/lib/commands/probe.js +70 -0
- package/lib/commands/reco.js +440 -0
- package/lib/commands/repl.js +1501 -0
- package/lib/commands/roi-check.js +273 -0
- package/lib/commands/roi-compare.js +388 -0
- package/lib/commands/runtime.js +718 -0
- package/lib/commands/skill.js +169 -0
- package/lib/commands/timing.js +247 -0
- package/lib/commands/version.js +66 -0
- package/lib/daemon/framed.mjs +3680 -0
- package/lib/daemon/reco_child.mjs +287 -0
- package/lib/exec.js +96 -0
- package/lib/flags.js +44 -0
- package/lib/image.js +90 -0
- package/lib/index.js +143 -0
- package/lib/interface/hotkey.js +142 -0
- package/lib/interface/index.js +12 -0
- package/lib/interface/jsonc.js +95 -0
- package/lib/interface/load.js +397 -0
- package/lib/interface/override.js +287 -0
- package/lib/interface/plan.js +279 -0
- package/lib/option-args.js +29 -0
- package/lib/pkg.js +54 -0
- package/lib/protocol.js +94 -0
- package/lib/runtime/actions.js +225 -0
- package/lib/runtime/keyframes.js +73 -0
- 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 +135 -0
- package/lib/runtime/signal-guard.js +35 -0
- package/lib/types/client/daemon.d.ts +86 -0
- package/lib/types/commands/annotate.d.ts +3 -0
- package/lib/types/commands/calibrate.d.ts +7 -0
- package/lib/types/commands/color.d.ts +9 -0
- package/lib/types/commands/crop.d.ts +3 -0
- package/lib/types/commands/dispatch.d.ts +22 -0
- package/lib/types/commands/env.d.ts +21 -0
- package/lib/types/commands/index.d.ts +13 -0
- package/lib/types/commands/input.d.ts +17 -0
- package/lib/types/commands/kf.d.ts +10 -0
- package/lib/types/commands/probe.d.ts +15 -0
- package/lib/types/commands/reco.d.ts +27 -0
- package/lib/types/commands/repl.d.ts +10 -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 +66 -0
- package/lib/types/commands/skill.d.ts +17 -0
- package/lib/types/commands/timing.d.ts +64 -0
- package/lib/types/commands/version.d.ts +3 -0
- package/lib/types/exec.d.ts +31 -0
- package/lib/types/flags.d.ts +62 -0
- package/lib/types/image.d.ts +27 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/interface/hotkey.d.ts +36 -0
- package/lib/types/interface/index.d.ts +11 -0
- package/lib/types/interface/jsonc.d.ts +9 -0
- package/lib/types/interface/load.d.ts +190 -0
- package/lib/types/interface/override.d.ts +78 -0
- package/lib/types/interface/plan.d.ts +68 -0
- package/lib/types/option-args.d.ts +19 -0
- package/lib/types/pkg.d.ts +14 -0
- package/lib/types/protocol.d.ts +124 -0
- package/lib/types/runtime/actions.d.ts +248 -0
- package/lib/types/runtime/keyframes.d.ts +43 -0
- 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 +77 -0
- package/lib/types/runtime/signal-guard.d.ts +19 -0
- package/lib/types/update-check.d.ts +20 -0
- package/lib/types/usage.d.ts +3 -0
- package/lib/update-check.js +97 -0
- package/lib/usage.js +50 -0
- package/package.json +94 -4
- package/skills/README.md +51 -0
- package/skills/maafw-live/SKILL.md +293 -0
- package/skills/maafw-live/references/examples.md +238 -0
- package/skills/maafw-live/references/pitfalls.md +211 -0
- package/skills/maafw-live/references/workflows.md +164 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { CommandResult } from '../protocol.js';
|
|
2
|
+
/** 会话名白名单:字母数字与 `-` `_`,1..48 字符。要进管道名与文件路径,必须收紧。 */
|
|
3
|
+
export declare function validSessionName(name: string): boolean;
|
|
4
|
+
/** 会话目录:`~/.maafw-live/sessions/<name>/`(与 frames 同级,都是本工具的本地状态)。 */
|
|
5
|
+
export declare function sessionDir(name: string): string;
|
|
6
|
+
/** Windows 命名管道名;其它平台用 Unix domain socket 文件(同一份代码,两个平台都能跑)。
|
|
7
|
+
*
|
|
8
|
+
* ⚠️ 返回 Unix socket **路径**时会落在 `sessionDir(name)/sock` 下,所以调用方必须先确保
|
|
9
|
+
* 该目录存在(`ensureSessionDir`)——`listen()` 对不存在的父目录直接 ENOENT。
|
|
10
|
+
* Windows 命名管道在 `\\.\pipe\` 命名空间里,与文件系统无关,不需要建目录。 */
|
|
11
|
+
export declare function sessionPipePath(name: string): string;
|
|
12
|
+
/** 平台无关的"管道父目录就绪":Windows 上是空操作(命名管道不走文件系统)。 */
|
|
13
|
+
export declare function ensureSessionDir(name: string): void;
|
|
14
|
+
export interface SessionMeta {
|
|
15
|
+
name: string;
|
|
16
|
+
pid: number;
|
|
17
|
+
pipe: string;
|
|
18
|
+
startedAt: string;
|
|
19
|
+
/** 起会话时给的连接参数,便于诊断"为什么这条会话连的是别的设备" */
|
|
20
|
+
connectArgs: Record<string, unknown>;
|
|
21
|
+
/** 起会话的 CLI 版本(npm test / npm install 升级后老会话仍跑旧 lib,版本钉进 meta 让"混版"可见) */
|
|
22
|
+
cliVersion?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function sessionMetaPath(name: string): string;
|
|
25
|
+
export declare function readSessionMeta(name: string): SessionMeta | null;
|
|
26
|
+
/** 进程还活着吗(`process.kill(pid, 0)` 不真发信号,只做存在性检查)。 */
|
|
27
|
+
export declare function pidAlive(pid: number): boolean;
|
|
28
|
+
/** 一条命令的请求/回执信封。`lines` 是 repl 该命令打出的全部输出行(按序)。
|
|
29
|
+
*
|
|
30
|
+
* 协议 v2:请求可以是 `line`(旧,走 REPL 行解释器)或 `exec`(argv,走 dispatchArgv
|
|
31
|
+
* 拿到结构化 CommandResult)——信封丢结构化结果(`--session` 下 `--json` 只剩 {name,pid})
|
|
32
|
+
* 就是因为只有 line 这条路。 */
|
|
33
|
+
export interface ServeRequest {
|
|
34
|
+
id: number;
|
|
35
|
+
/** 缺省按 line 处理(兼容旧调用方) */
|
|
36
|
+
kind?: 'line' | 'exec';
|
|
37
|
+
line?: string;
|
|
38
|
+
argv?: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface ServeReply {
|
|
41
|
+
id: number;
|
|
42
|
+
ok: boolean;
|
|
43
|
+
lines: string[];
|
|
44
|
+
error?: string;
|
|
45
|
+
/** exec 请求的结构化结果(v2);line 请求没有这个字段 */
|
|
46
|
+
result?: CommandResult;
|
|
47
|
+
/** `quit`/`exit` 之后常驻进程会自行收尾 */
|
|
48
|
+
closing?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** 常驻侧回执的**调用方可见部分**:`lines` 由服务端从已收集的输出填,调用方不必传
|
|
51
|
+
* (显式给 `lines: []` 也不报错——服务端一律以自己的缓冲为准,避免两处都以为自己说了算)。 */
|
|
52
|
+
export interface ServeReplyInput {
|
|
53
|
+
ok: boolean;
|
|
54
|
+
lines?: string[];
|
|
55
|
+
error?: string;
|
|
56
|
+
/** exec 请求的结构化结果(v2) */
|
|
57
|
+
result?: CommandResult;
|
|
58
|
+
/** `quit`/`exit` 之后常驻进程会自行收尾 */
|
|
59
|
+
closing?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** 常驻侧:把 repl 的输出按"命令"切段回给调用方。 */
|
|
62
|
+
export declare class SessionServer {
|
|
63
|
+
private readonly onLine;
|
|
64
|
+
private readonly onExec?;
|
|
65
|
+
private server;
|
|
66
|
+
private readonly sockets;
|
|
67
|
+
/** 串行队列:会话共用一条设备连接,命令必须一条条跑完再下一条 */
|
|
68
|
+
private readonly pending;
|
|
69
|
+
private buffer;
|
|
70
|
+
private current;
|
|
71
|
+
/**
|
|
72
|
+
* @param onLine REPL 行解释器(旧 kind=line 路径)
|
|
73
|
+
* @param onExec argv 分发器(kind=exec):注入常驻 client 后走 dispatchArgv,
|
|
74
|
+
* 回执里带结构化 CommandResult——`--session` 下的 --json 信封靠它才存在。
|
|
75
|
+
* 不给时 exec 请求回 UNSUPPORTED(防"静默按 line 跑了别的命令")。
|
|
76
|
+
*/
|
|
77
|
+
constructor(onLine: (line: string, reply: (r: ServeReplyInput) => void) => void, onExec?: ((argv: string[], reply: (r: ServeReplyInput) => void) => void) | undefined);
|
|
78
|
+
/** 供 repl 循环调用:把一行输出归到当前命令的回执里。
|
|
79
|
+
*
|
|
80
|
+
* **没有当前命令时不是丢弃,而是挂到"待认领"队列**:`run` 这类命令是异步收尾的
|
|
81
|
+
* (提交后立刻返回,`run 完成…` 稍后才打),而回执在那之前就发出去了——丢掉的话
|
|
82
|
+
* 调用方永远看不到任务结果。挂起来随下一条命令的回执一起给,轮询式用法正好能拿到。
|
|
83
|
+
* 队列有上限:没人认领时不能无限涨(常驻会话可能几天没人问)。 */
|
|
84
|
+
pushOutput(line: string): void;
|
|
85
|
+
/** 连接横幅等:等价于"还没有命令时打的输出",与异步收尾共用同一条待认领队列。 */
|
|
86
|
+
pushStartup(line: string): void;
|
|
87
|
+
private readonly unclaimed;
|
|
88
|
+
private reply;
|
|
89
|
+
listen(pipe: string): Promise<void>;
|
|
90
|
+
close(): void;
|
|
91
|
+
}
|
|
92
|
+
/** 调用侧:把一条命令送到已存在的会话,等它那条回执。 */
|
|
93
|
+
export declare function sendToSession(meta: SessionMeta, line: string, timeoutMs: number): Promise<ServeReply>;
|
|
94
|
+
/** 调用侧(v2):把一条 argv 送到会话,等它那条带结构化 result 的回执。
|
|
95
|
+
* 与 sendToSession 共用同一条管道/协议,只是 kind/载荷不同。 */
|
|
96
|
+
export declare function sendToSessionExec(meta: SessionMeta, argv: string[], timeoutMs: number): Promise<ServeReply>;
|
|
97
|
+
/** 起一个脱离父进程的常驻会话(`--session-serve` 模式)。
|
|
98
|
+
*
|
|
99
|
+
* `stdio` 不能是 `'ignore'`:常驻进程起不来时(最典型的是 POSIX 上残留 socket 导致
|
|
100
|
+
* listen 失败),调用方只会看到"管道 20s 内不可连",真因被吞掉——而 `listen` 的 EADDRINUSE
|
|
101
|
+
* 与"连接参数错"是完全不同的两件事。所以把它自己的 stdout/stderr 落到会话目录的
|
|
102
|
+
* `serve.log`,失败时读回来当诊断依据。 */
|
|
103
|
+
export declare function spawnSessionServer(name: string, cliEntry: string, connectArgs: Record<string, unknown>): SessionMeta;
|
|
104
|
+
/** 读常驻进程自己的日志尾部(诊断"为什么起不来")。 */
|
|
105
|
+
export declare function sessionServeLogTail(name: string, lines?: number): string[];
|
|
106
|
+
/** 连接参数 → argv(会话常驻进程用同一套 CLI 旗标启动,不另造一套参数面)。
|
|
107
|
+
* 布尔值只发裸旗标:`--raw true` 里的 `true` 会被 parseArgs 当成位置参数(用法错误),
|
|
108
|
+
* 而且"false"是字符串真值,传成参数就把语义反了。 */
|
|
109
|
+
export declare function connectArgsToArgv(a: Record<string, unknown>): string[];
|
|
110
|
+
/** 等管道可连(常驻进程起来要时间;轮询到超时为止,不靠固定 sleep)。 */
|
|
111
|
+
export declare function waitPipeReady(pipe: string, timeoutMs: number): Promise<boolean>;
|
|
112
|
+
/** 收掉一条会话(清 meta 与目录,管道随进程退出消失)。
|
|
113
|
+
* 进程树:server → daemon → agent(uv → venv-python → uv-python)。process.kill 只打
|
|
114
|
+
* 第一层——daemon 来不及收摊就死,agent 树整体孤儿化(实测两层 python 残留)。
|
|
115
|
+
* taskkill /T 按父链整棵收;uv 重 exec 出的中层会脱离父链漏掉,再补一刀:命令行带
|
|
116
|
+
* agent/main.py 且父进程已死的全是孤儿(活会话的 agent 父链必健在,不会误伤)。 */
|
|
117
|
+
export declare function stopSession(name: string): void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话装配:一次性命令如何在同一个 daemon 里先连上设备、再干活、最后收尾。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它:daemon 的连接是进程态——命令跑完进程就退出,连接随之消失。所以涉及设备的
|
|
5
|
+
* 命令(screencap / frame / run / click…)必须能在同一次调用里先完成连接,否则它们只会在
|
|
6
|
+
* REPL 里可用。传入 --project(推荐,走 interface.json 规划)或 --kind/--target(手动)
|
|
7
|
+
* 即可;两者都不给时交给命令自己处理。
|
|
8
|
+
*/
|
|
9
|
+
import type { DaemonClient } from '../client/daemon.js';
|
|
10
|
+
import { type ProjectPlan } from './actions.js';
|
|
11
|
+
export interface SessionOptions {
|
|
12
|
+
/** Maa 项目目录:按 interface.json 的 controller[] 规划连接 */
|
|
13
|
+
project?: string;
|
|
14
|
+
/** 项目模式下指定控制器名 */
|
|
15
|
+
controller?: string;
|
|
16
|
+
resource?: string;
|
|
17
|
+
/** 项目模式下的窗口 / adb 地址覆盖(持久化选择由面板负责,CLI 不做持久化) */
|
|
18
|
+
hwnd?: string;
|
|
19
|
+
address?: string;
|
|
20
|
+
/** 手动路径 */
|
|
21
|
+
kind?: string;
|
|
22
|
+
target?: string;
|
|
23
|
+
shortSide?: number;
|
|
24
|
+
longSide?: number;
|
|
25
|
+
rawSize?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* 跑 interface.json 的 pretask。**显式开启**:只有"启动工作"类命令(run / 交互式与命名
|
|
28
|
+
* 会话的起连 / REPL connect project)该跑——观测与输入类命令(click / frame / wait / screencap)
|
|
29
|
+
* 经 ensureSession 隐式连接,跑一遍"重启游戏"类 pretask 会把正要观测的现场毁掉(设计审查 P0-2)。
|
|
30
|
+
* `false` 也显式关闭(`--no-pretask`)。未传 = 不跑。
|
|
31
|
+
*/
|
|
32
|
+
pretask?: boolean;
|
|
33
|
+
/** run 的按次取值:pretask 的末位 JSON 也用这两份 */
|
|
34
|
+
optionCases?: Record<string, string | string[]>;
|
|
35
|
+
optionFields?: Record<string, Record<string, string>>;
|
|
36
|
+
/** CLI 的 --cwd:`--resource` 给相对路径时按它解析(不是 interface.json 目录、不是 process.cwd()) */
|
|
37
|
+
cwd?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface SessionState {
|
|
40
|
+
connected: boolean;
|
|
41
|
+
plan?: ProjectPlan;
|
|
42
|
+
session?: Record<string, unknown> | null;
|
|
43
|
+
notes?: string[];
|
|
44
|
+
/** 本次连接重建了控制器、因而停掉了上一轮的帧流(daemon 回执 streamStopped):调用方要接着看帧就重开流 */
|
|
45
|
+
streamStopped?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export declare class SessionError extends Error {
|
|
48
|
+
readonly code: string;
|
|
49
|
+
constructor(message: string, code: string);
|
|
50
|
+
}
|
|
51
|
+
/** 需要设备时连一次;项目模式下先规划控制器再连(拒绝猜设备)。 */
|
|
52
|
+
export declare function ensureSession(client: DaemonClient, o: SessionOptions): Promise<SessionState>;
|
|
53
|
+
/**
|
|
54
|
+
* 离线资源解析:只读 interface.json 拿资源目录,不扫设备、不连接。
|
|
55
|
+
*
|
|
56
|
+
* 为什么需要它:从关键帧库留存帧裁模板这条动线**不需要设备**(这正是"不可复现状态"的意义——
|
|
57
|
+
* 设备早已不在那个画面上)。但自匹配要加载资源,而资源声明在项目里。项目此刻只是资源的
|
|
58
|
+
* 来源,不是连接的对象,所以走 interface 的规划函数而不走 planProject(后者要扫设备)。
|
|
59
|
+
*
|
|
60
|
+
* 返回的是**整条链**(`resource.path[]` 按序叠加,末尾追加当前控制器的 `attach_resource_path`),
|
|
61
|
+
* 与在线规划同一份 `resolveResourcePaths`:只写 `resource.path` 会让 attach 进来的一段凭空消失。
|
|
62
|
+
*/
|
|
63
|
+
export declare function offlineResource(project: string, controller?: string, resource?: string,
|
|
64
|
+
/** `--resource` 相对路径的解析基准(CLI 的 --cwd) */
|
|
65
|
+
cwd?: string): {
|
|
66
|
+
paths: string[];
|
|
67
|
+
names: string[];
|
|
68
|
+
selected: string | null;
|
|
69
|
+
controllerName: string | null;
|
|
70
|
+
error?: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* 连接结果的行渲染(human 输出与 --json 共用一份事实)。
|
|
74
|
+
* `verbose=false` 时压制 `note:` 行(多资源选择、连接来源等建议性说明)——
|
|
75
|
+
* 一次性命令每条都打同一串 note 是噪音;事实行(已连接/分辨率/资源路径)不受门控。
|
|
76
|
+
*/
|
|
77
|
+
export declare function describeSession(s: SessionState, verbose?: boolean): string[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run 阻塞期信号处理(审计 #6/#9):Ctrl-C / SIGTERM 时不许让 daemon 里的任务裸跑。
|
|
3
|
+
*
|
|
4
|
+
* 第一下:向 daemon 发 run_stop(post_stop),run 回执照常落盘,退出码走 128+signo
|
|
5
|
+
* (SIGINT=130 / SIGTERM=143)。第二下:直接退出——第一次没杀掉说明 daemon 已不应答,
|
|
6
|
+
* 再等没有现场可留(daemon 侧另有 onHostGone 兜底清 agent 树)。
|
|
7
|
+
*
|
|
8
|
+
* 为什么抽出来:Windows 没法跨进程投递 Ctrl-C,内联在命令里就没法测;
|
|
9
|
+
* process.emit('SIGINT') 会同步触发监听,单测只验"旗标立了、run_stop 发了"。
|
|
10
|
+
*/
|
|
11
|
+
import { type ExitCode } from '../protocol.js';
|
|
12
|
+
export interface RunSignalGuard {
|
|
13
|
+
/** 当前命中的信号对应的退出码;0 = 没被打断 */
|
|
14
|
+
readonly code: ExitCode | 0;
|
|
15
|
+
/** 摘钩子并返回最终 code(run 回执渲染前调用) */
|
|
16
|
+
restore(): ExitCode | 0;
|
|
17
|
+
}
|
|
18
|
+
/** 挂载信号守卫;`runStop` 是"给 daemon 发停止"的那个异步调用。 */
|
|
19
|
+
export declare function installRunSignalGuard(runStop: () => Promise<unknown>): RunSignalGuard;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface UpdateState {
|
|
2
|
+
/** 上次成功查询的时间(epoch ms) */
|
|
3
|
+
checkedAt: number;
|
|
4
|
+
/** 上次查到的 latest */
|
|
5
|
+
latest: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const UPDATE_TTL_MS: number;
|
|
8
|
+
/** registry 的 packument URL:scoped 名只编码斜杠(`@scope` 保持字面) */
|
|
9
|
+
export declare function packumentUrl(name: string): string;
|
|
10
|
+
/** 语义化版本比较(只按 x.y.z 数值段;预发布标记按 semver 规则"有则更低")。返回 a-b 的符号。 */
|
|
11
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
12
|
+
/** 缓存是否还新鲜(没有缓存、或时间戳比 TTL 旧 → 该查) */
|
|
13
|
+
export declare function shouldCheck(state: UpdateState | null, now: number, ttlMs?: number): boolean;
|
|
14
|
+
export declare function statePath(runDir?: string): string;
|
|
15
|
+
export declare function readState(file?: string): UpdateState | null;
|
|
16
|
+
export declare function writeState(state: UpdateState, file?: string): void;
|
|
17
|
+
/** 自动检查是否被环境变量关掉 */
|
|
18
|
+
export declare function updateCheckDisabled(env?: NodeJS.ProcessEnv): boolean;
|
|
19
|
+
/** 查 registry 的 latest。任何失败都返回 null(静默)。 */
|
|
20
|
+
export declare function fetchLatest(name: string, timeoutMs?: number): Promise<string | null>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 更新检查:本机版本 vs registry 上的 latest。
|
|
3
|
+
*
|
|
4
|
+
* 设计取舍(刻意做成"只报不换"):
|
|
5
|
+
* - **不自动切换运行时**:agent 的长任务跑到一半换版本,比"晚一天升级"风险大得多。这里只提示。
|
|
6
|
+
* - **只在交互终端自动查**:管道/agent 调用不为它付代价(`version --check` 是显式请求,不受此限)。
|
|
7
|
+
* - **限频**:结果落盘缓存(默认 24h),不每次敲命令都打网络。
|
|
8
|
+
* - **可关**:`MAAFW_LIVE_NO_UPDATE_CHECK=1`。
|
|
9
|
+
* - **失败一律静默**:离线、代理、registry 抖动都不该影响 `version` 本身——它没有变得"失败"。
|
|
10
|
+
*/
|
|
11
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { dirname, join } from 'node:path';
|
|
13
|
+
import { defaultRunDir } from './client/daemon.js';
|
|
14
|
+
export const UPDATE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
15
|
+
const FETCH_TIMEOUT_MS = 3000;
|
|
16
|
+
/** registry 的 packument URL:scoped 名只编码斜杠(`@scope` 保持字面) */
|
|
17
|
+
export function packumentUrl(name) {
|
|
18
|
+
return 'https://registry.npmjs.org/' + name.replace('/', '%2F');
|
|
19
|
+
}
|
|
20
|
+
/** 语义化版本比较(只按 x.y.z 数值段;预发布标记按 semver 规则"有则更低")。返回 a-b 的符号。 */
|
|
21
|
+
export function compareVersions(a, b) {
|
|
22
|
+
const parse = (v) => {
|
|
23
|
+
const [core, ...rest] = String(v).trim().split('-');
|
|
24
|
+
const nums = core.split('.').map((x) => {
|
|
25
|
+
const n = Number(x);
|
|
26
|
+
return Number.isFinite(n) ? n : 0;
|
|
27
|
+
});
|
|
28
|
+
while (nums.length < 3)
|
|
29
|
+
nums.push(0);
|
|
30
|
+
return { nums: nums.slice(0, 3), pre: rest.length ? rest.join('-') : null };
|
|
31
|
+
};
|
|
32
|
+
const A = parse(a), B = parse(b);
|
|
33
|
+
for (let i = 0; i < 3; i++) {
|
|
34
|
+
if (A.nums[i] !== B.nums[i])
|
|
35
|
+
return A.nums[i] > B.nums[i] ? 1 : -1;
|
|
36
|
+
}
|
|
37
|
+
/* 主版本相同时:预发布低于正式版(1.0.0-rc < 1.0.0) */
|
|
38
|
+
if (A.pre === B.pre)
|
|
39
|
+
return 0;
|
|
40
|
+
if (A.pre === null)
|
|
41
|
+
return 1;
|
|
42
|
+
if (B.pre === null)
|
|
43
|
+
return -1;
|
|
44
|
+
return A.pre > B.pre ? 1 : -1;
|
|
45
|
+
}
|
|
46
|
+
/** 缓存是否还新鲜(没有缓存、或时间戳比 TTL 旧 → 该查) */
|
|
47
|
+
export function shouldCheck(state, now, ttlMs = UPDATE_TTL_MS) {
|
|
48
|
+
if (!state || typeof state.checkedAt !== 'number' || !Number.isFinite(state.checkedAt))
|
|
49
|
+
return true;
|
|
50
|
+
if (typeof state.latest !== 'string' || state.latest === '')
|
|
51
|
+
return true;
|
|
52
|
+
return now - state.checkedAt >= ttlMs;
|
|
53
|
+
}
|
|
54
|
+
export function statePath(runDir = defaultRunDir()) {
|
|
55
|
+
return join(runDir, 'version-check.json');
|
|
56
|
+
}
|
|
57
|
+
export function readState(file = statePath()) {
|
|
58
|
+
try {
|
|
59
|
+
const j = JSON.parse(readFileSync(file, 'utf8'));
|
|
60
|
+
return j && typeof j.checkedAt === 'number' && typeof j.latest === 'string' ? j : null;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function writeState(state, file = statePath()) {
|
|
67
|
+
try {
|
|
68
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
69
|
+
writeFileSync(file, JSON.stringify(state, null, 2) + '\n');
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* 缓存写不进去不影响任何事 */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** 自动检查是否被环境变量关掉 */
|
|
76
|
+
export function updateCheckDisabled(env = process.env) {
|
|
77
|
+
const v = String(env.MAAFW_LIVE_NO_UPDATE_CHECK ?? '').trim();
|
|
78
|
+
return v !== '' && v !== '0' && v.toLowerCase() !== 'false';
|
|
79
|
+
}
|
|
80
|
+
/** 查 registry 的 latest。任何失败都返回 null(静默)。 */
|
|
81
|
+
export async function fetchLatest(name, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(packumentUrl(name), {
|
|
84
|
+
headers: { accept: 'application/vnd.npm.install-v1+json' },
|
|
85
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok)
|
|
88
|
+
return null;
|
|
89
|
+
const j = (await res.json());
|
|
90
|
+
const latest = j['dist-tags']?.latest;
|
|
91
|
+
return typeof latest === 'string' && latest !== '' ? latest : null;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=update-check.js.map
|
package/lib/usage.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** 帮助文本。全局选项的说明集中在这里,命令只声明自己的选项。 */
|
|
2
|
+
import { COMMANDS } from './commands/index.js';
|
|
3
|
+
import { cliPackage } from './pkg.js';
|
|
4
|
+
const GLOBAL_HELP = [
|
|
5
|
+
['--json', '输出统一 JSON 信封(stdout 保持纯 JSON)'],
|
|
6
|
+
['--yes', '授权标志(当前版本无确认步骤,照传)'],
|
|
7
|
+
['--no-interactive', '防交互挂住自动化(当前版本无提问,照传)'],
|
|
8
|
+
['--no-color', '关闭颜色(管道下自动关闭)'],
|
|
9
|
+
['--cwd <dir>', '指定工作目录'],
|
|
10
|
+
['--dry-run', '未实现:传了退 2(不接受假装生效)'],
|
|
11
|
+
['--limit <n>', '未实现:传了退 2(events 有自己的 --limit)'],
|
|
12
|
+
['--verbose', '未实现:传了退 2'],
|
|
13
|
+
['-h, --help', '显示帮助'],
|
|
14
|
+
['-V, --version', '显示版本'],
|
|
15
|
+
];
|
|
16
|
+
function pad(s, n) {
|
|
17
|
+
return s.length >= n ? s : s + ' '.repeat(n - s.length);
|
|
18
|
+
}
|
|
19
|
+
export function topLevelUsage() {
|
|
20
|
+
const pkg = cliPackage();
|
|
21
|
+
const nameWidth = Math.max(...COMMANDS.map((c) => c.name.length), 7);
|
|
22
|
+
return [
|
|
23
|
+
`${pkg.name} ${pkg.version} — MaaFramework 设备运行时与观测底座`,
|
|
24
|
+
'',
|
|
25
|
+
'用法:',
|
|
26
|
+
' maafw-live <command> [options]',
|
|
27
|
+
'',
|
|
28
|
+
'命令:',
|
|
29
|
+
...COMMANDS.map((c) => ` ${pad(c.name, nameWidth + 2)}${c.summary}`),
|
|
30
|
+
'',
|
|
31
|
+
'全局选项:',
|
|
32
|
+
...GLOBAL_HELP.map(([f, d]) => ` ${pad(f, 20)}${d}`),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
export function commandUsage(cmd) {
|
|
36
|
+
const lines = [cmd.summary, '', '用法:', ' ' + cmd.usage];
|
|
37
|
+
const own = Object.entries(cmd.options ?? {});
|
|
38
|
+
if (own.length > 0) {
|
|
39
|
+
lines.push('', '选项:');
|
|
40
|
+
for (const [flag, spec] of own) {
|
|
41
|
+
const isBool = spec.type === 'boolean';
|
|
42
|
+
lines.push(` ${pad('--' + flag + (isBool ? '' : ' <value>'), 20)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
lines.push('', '全局选项:');
|
|
46
|
+
for (const [f, d] of GLOBAL_HELP)
|
|
47
|
+
lines.push(` ${pad(f, 20)}${d}`);
|
|
48
|
+
return lines;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=usage.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,96 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windsland52/maafw-live",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "MaaFramework 设备交互与实时观测底座:常驻 daemon + 可复用客户端 + 确定性 CLI。不绑定宿主。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"maafw-live": "bin/maafw-live.mjs"
|
|
8
|
+
},
|
|
9
|
+
"main": "./lib/index.js",
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"bin",
|
|
13
|
+
"lib",
|
|
14
|
+
"!lib/**/*.map",
|
|
15
|
+
"skills",
|
|
16
|
+
"docs/daemon-protocol.md",
|
|
17
|
+
"docs/keyframe-retention-contract-v0.md",
|
|
18
|
+
"docs/session-unify-v0.md"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Windsland52/maafw-live.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/Windsland52/maafw-live#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/Windsland52/maafw-live/issues"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"maaframework",
|
|
30
|
+
"maa",
|
|
31
|
+
"automation",
|
|
32
|
+
"agent",
|
|
33
|
+
"adb",
|
|
34
|
+
"win32",
|
|
35
|
+
"cli",
|
|
36
|
+
"daemon"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"exports": {
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./lib/types/index.d.ts",
|
|
44
|
+
"default": "./lib/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./client": {
|
|
47
|
+
"types": "./lib/types/client/daemon.d.ts",
|
|
48
|
+
"default": "./lib/client/daemon.js"
|
|
49
|
+
},
|
|
50
|
+
"./actions": {
|
|
51
|
+
"types": "./lib/types/runtime/actions.d.ts",
|
|
52
|
+
"default": "./lib/runtime/actions.js"
|
|
53
|
+
},
|
|
54
|
+
"./keyframes": {
|
|
55
|
+
"types": "./lib/types/runtime/keyframes.d.ts",
|
|
56
|
+
"default": "./lib/runtime/keyframes.js"
|
|
57
|
+
},
|
|
58
|
+
"./reco-args": {
|
|
59
|
+
"types": "./lib/types/runtime/reco-args.d.ts",
|
|
60
|
+
"default": "./lib/runtime/reco-args.js"
|
|
61
|
+
},
|
|
62
|
+
"./image": {
|
|
63
|
+
"types": "./lib/types/image.d.ts",
|
|
64
|
+
"default": "./lib/image.js"
|
|
65
|
+
},
|
|
66
|
+
"./package.json": "./package.json"
|
|
67
|
+
},
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=22.13"
|
|
70
|
+
},
|
|
71
|
+
"license": "MIT",
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "tsc -p tsconfig.json && node scripts/copy-daemon.mjs",
|
|
74
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
75
|
+
"verify": "node scripts/verify-cli.mjs",
|
|
76
|
+
"test": "npm run build && node scripts/run-tests.mjs",
|
|
77
|
+
"test:all": "npm run build && node scripts/run-tests.mjs --group all",
|
|
78
|
+
"test:device": "npm run build && node scripts/run-tests.mjs --group device",
|
|
79
|
+
"test:contract": "npm run build && node scripts/run-tests.mjs --group contract",
|
|
80
|
+
"skill:check": "node scripts/skill-check.mjs",
|
|
81
|
+
"accept:offline": "node scripts/accept-kf-offline.mjs",
|
|
82
|
+
"accept:crop": "node scripts/accept-crop-fromkf.mjs",
|
|
83
|
+
"accept:roi-compare": "node scripts/accept-roi-compare.mjs",
|
|
84
|
+
"accept:reco-node": "node scripts/accept-reco-node.mjs",
|
|
85
|
+
"survey:crop": "node scripts/survey-crop.mjs",
|
|
86
|
+
"survey:som": "node scripts/survey-som.mjs",
|
|
87
|
+
"accept": "node scripts/accept-emulator.mjs"
|
|
88
|
+
},
|
|
89
|
+
"dependencies": {
|
|
90
|
+
"@maaxyz/maa-node": "5.14.2"
|
|
91
|
+
},
|
|
92
|
+
"devDependencies": {
|
|
93
|
+
"@types/node": "^24.13.3",
|
|
94
|
+
"typescript": "^5.9.0"
|
|
95
|
+
}
|
|
96
|
+
}
|
package/skills/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# maafw-live Skills
|
|
2
|
+
|
|
3
|
+
本目录是本仓随 npm 包一起发布的 Agent Skill。每个 skill 一个目录,含 `SKILL.md` 与可选的 `references/`。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
Skill 与 CLI 分开安装:装 CLI 不等于装 skill(反之亦然)。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# CLI(skill 里所有命令都要它;装 scoped 包名,敲裸命令名)
|
|
11
|
+
npm install --global @windsland52/maafw-live@latest
|
|
12
|
+
maafw-live version
|
|
13
|
+
|
|
14
|
+
# skill(标准 skills CLI;列仓库里有哪些:--list)
|
|
15
|
+
npx skills add https://github.com/Windsland52/maafw-live --list
|
|
16
|
+
npx skills add https://github.com/Windsland52/maafw-live --skill maafw-live --global
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
本地开发时用 checkout 路径:`npx skills add . --skill maafw-live`(本地安装不跟随远端更新,改完重跑)。
|
|
20
|
+
|
|
21
|
+
## Skill 与 CLI 的版本关系
|
|
22
|
+
|
|
23
|
+
Skill 随包发布但**不写自己的版本号**——写死的版本每次发版都要改,而且从仓库装的副本仍然对不上。
|
|
24
|
+
所以对齐用**比字节**,CLI 自带四条命令:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
maafw-live skill # 包内 skill 位置与逐文件指纹
|
|
28
|
+
maafw-live skill --check <安装根目录> # 比对 <根>/maafw-live 与包内副本:same/different/missing/extra,有漂移退出 3
|
|
29
|
+
maafw-live skill --install <根目录> # 把包内副本逐字节写到 <根>/maafw-live(离线,不联网)
|
|
30
|
+
maafw-live skill --print [--format json] # 输出包内副本内容(harness 自取,不想用 skills CLI 时)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`extra`(目标目录多出来的文件,例如 skills CLI 放进去的 agent 元数据)**不计漂移**;仅行尾不同(CRLF ↔ LF)
|
|
34
|
+
仍算 `different`,但会标注成因,避免把工具改写误判成内容分叉。日常安装/更新仍推荐 skills CLI
|
|
35
|
+
(它维护各 agent 的目录与符号链接),`--install` 是给"自己管一份副本"的 harness 用的。
|
|
36
|
+
|
|
37
|
+
Skill 与文档冲突时,**以本仓 `docs/` 为准**:
|
|
38
|
+
|
|
39
|
+
| 要知道什么 | 唯一出处 |
|
|
40
|
+
| :--- | :--- |
|
|
41
|
+
| daemon 命令、参数、返回字段 | [`docs/daemon-protocol.md`](../docs/daemon-protocol.md) |
|
|
42
|
+
| 关键帧留存与引用语义(L0/L1/L2、升格、出处) | [`docs/keyframe-retention-contract-v0.md`](../docs/keyframe-retention-contract-v0.md) |
|
|
43
|
+
| CLI 命令面、退出码、JSON 信封 | [`README.md`](../README.md) |
|
|
44
|
+
| 字段/协议/版本的原始语义(不属于本工具) | MaaLLMWiki(版本锁定原文) |
|
|
45
|
+
|
|
46
|
+
## 可用 Skill
|
|
47
|
+
|
|
48
|
+
### `maafw-live`
|
|
49
|
+
|
|
50
|
+
MaaFramework 应用的实时观测、识别实测、模板资产与状态证据留存。设备在线时给确定性观测;
|
|
51
|
+
设备不在那个画面上(或根本没连)时,从关键帧库留存帧离线裁出带出处的模板。
|