@harmonyos-arkts/d2h 0.1.5 → 0.1.7
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 +19 -18
- package/agents/android-to-hmos-00-orchestrator.md +1 -1
- package/agents/d2h.md +101 -285
- package/dist/bin/install-opencode.js +9 -4
- package/dist/src/install-opencode.js +20 -1
- package/dist/src/install-vscode.js +6 -3
- package/dist/src/metrics-plugin.js +20 -0
- package/dist/src/toolchain.js +16 -12
- package/package.json +1 -1
- package/scripts/check-environment.mjs +1 -2
- package/skills/d2h-hypium-forge/SKILL.md +59 -2
- package/skills/d2h-hypium-forge/scripts/_common_template.py +85 -40
- package/skills/d2h-hypium-forge/scripts/analyze_results.py +11 -2
- package/skills/d2h-hypium-forge/scripts/probe.py +197 -0
- package/skills/d2h-hypium-forge/scripts/run_direct.py +464 -33
- package/skills/d2h-page-develop/SKILL.md +112 -0
- package/skills/d2h-page-develop/agents/openai.yaml +7 -0
- package/skills/d2h-page-spec-generate/SKILL.md +206 -0
- package/skills/d2h-page-spec-generate/scripts/build_batch_report.py +227 -0
- package/skills/managing-d2h-toolchain/SKILL.md +14 -13
- package/skills/managing-d2h-toolchain/scripts/manage-toolchain.mjs +98 -0
- package/skills/appgraph-rule-audit/SKILL.md +0 -77
- package/skills/appgraph-rule-audit/references/audit-contract.md +0 -47
- package/skills/appgraph-rule-audit/references/recommendation-schema.md +0 -41
- package/skills/appgraph-rule-audit/schemas/rule-opportunities.schema.json +0 -125
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""probe.py — 单设备快速取证探针(替代手写 UiDriver 内联脚本)。
|
|
3
|
+
|
|
4
|
+
背景(Grit 实证):测试阶段 69 次手工探针 + 35 次单例调试里,agent 反复手写
|
|
5
|
+
`python3 - <<EOF from hypium import ...` 取证——每次 10-20 行代码生成 + connect 样板,
|
|
6
|
+
占探针时间的一半以上。本工具把高频取证动作固化为一条命令,输出紧凑可直接读。
|
|
7
|
+
|
|
8
|
+
用法(--device 缺省取 env FORGE_DEVICE,再缺省 127.0.0.1:5555):
|
|
9
|
+
python3 probe.py texts # 当前页可见 Text/Button 文案
|
|
10
|
+
python3 probe.py ids # 当前页全部 key/id
|
|
11
|
+
python3 probe.py find <id> # 定位: 属性+bounds+可见性
|
|
12
|
+
python3 probe.py tree [--grep x] [--depth 6] # 紧凑组件树(含 type/key/text/bounds)
|
|
13
|
+
python3 probe.py click <id|text> # 点击: id 优先,文本兜底(uinput 坐标, Toggle 实证可靠)
|
|
14
|
+
python3 probe.py tap <x> <y> # 原始坐标点击 (uinput -T -c)
|
|
15
|
+
python3 probe.py shot [out.jpeg] # 截图
|
|
16
|
+
python3 probe.py hilog [tag] [n] # hilog 过滤 (缺省 JSAPP/400)
|
|
17
|
+
python3 probe.py db-pull <package> # 拉取应用 rdb 目录到 ./db_probe/
|
|
18
|
+
python3 probe.py db-sql <db> <sql> # 本地 sqlite 查询(db-pull 之后)
|
|
19
|
+
|
|
20
|
+
除 click/shot 外全部基于单次 uitest dumpLayout 本地解析(1 次 RPC 取全量证据)。
|
|
21
|
+
"""
|
|
22
|
+
import argparse
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import re
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
import tempfile
|
|
29
|
+
|
|
30
|
+
LAYOUT_REMOTE = '/data/local/tmp/_probe_layout.json'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def hdc_bin():
|
|
34
|
+
return os.environ.get('FORGE_HDC', 'hdc')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def hdc(dev, args, timeout=25):
|
|
38
|
+
r = subprocess.run([hdc_bin(), '-t', dev] + args,
|
|
39
|
+
capture_output=True, timeout=timeout)
|
|
40
|
+
return (r.stdout + r.stderr).decode('utf-8', 'ignore')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def dump_layout(dev, timeout=25):
|
|
44
|
+
"""单次 RPC: uitest dumpLayout → 本地 JSON dict(失败返回 {})。"""
|
|
45
|
+
local = os.path.join(tempfile.gettempdir(), '_probe_layout.json')
|
|
46
|
+
hdc(dev, ['shell', 'uitest', 'dumpLayout', '-p', LAYOUT_REMOTE], timeout)
|
|
47
|
+
hdc(dev, ['file', 'recv', LAYOUT_REMOTE, local], timeout)
|
|
48
|
+
try:
|
|
49
|
+
return json.load(open(local, encoding='utf-8'))
|
|
50
|
+
except Exception:
|
|
51
|
+
return {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def walk(node):
|
|
55
|
+
yield node
|
|
56
|
+
for ch in node.get('children') or []:
|
|
57
|
+
yield from walk(ch)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def attrs(n):
|
|
61
|
+
a = n.get('attributes') or {}
|
|
62
|
+
return a.get('type', '?'), a.get('key', '') or '', a.get('text', '') or '', a.get('bounds', '')
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def cmd_texts(dev, _a):
|
|
66
|
+
seen, out = set(), []
|
|
67
|
+
for n in walk(dump_layout(dev)):
|
|
68
|
+
t, k, txt, b = attrs(n)
|
|
69
|
+
if txt and txt not in seen:
|
|
70
|
+
seen.add(txt)
|
|
71
|
+
out.append(f'{txt} [{b}]')
|
|
72
|
+
print('\n'.join(out[:80]) or '(无文本)')
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def cmd_ids(dev, _a):
|
|
76
|
+
out = []
|
|
77
|
+
for n in walk(dump_layout(dev)):
|
|
78
|
+
t, k, txt, b = attrs(n)
|
|
79
|
+
if k:
|
|
80
|
+
out.append(f'{k} ({t}{"/" + txt if txt else ""}) [{b}]')
|
|
81
|
+
print('\n'.join(out[:120]) or '(无 id)')
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def cmd_find(dev, a):
|
|
85
|
+
target = a.target
|
|
86
|
+
for n in walk(dump_layout(dev)):
|
|
87
|
+
t, k, txt, b = attrs(n)
|
|
88
|
+
if k == target or txt == target:
|
|
89
|
+
print(f'type={t} key={k} text={txt} bounds={b} visible={bool(b)}')
|
|
90
|
+
return
|
|
91
|
+
print(f'not found: {target}')
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def cmd_tree(dev, a):
|
|
95
|
+
root = dump_layout(dev)
|
|
96
|
+
lines = []
|
|
97
|
+
|
|
98
|
+
def rec(n, depth):
|
|
99
|
+
if depth > a.depth:
|
|
100
|
+
return
|
|
101
|
+
t, k, txt, b = attrs(n)
|
|
102
|
+
tag = t + (f'#{k}' if k else '') + (f' "{txt[:14]}"' if txt else '')
|
|
103
|
+
if (not a.grep or a.grep in k or a.grep in txt or a.grep in t):
|
|
104
|
+
lines.append(' ' * depth + f'{tag} {b}')
|
|
105
|
+
for ch in n.get('children') or []:
|
|
106
|
+
rec(ch, depth + 1)
|
|
107
|
+
|
|
108
|
+
rec(root, 0)
|
|
109
|
+
print('\n'.join(lines[:200]) or '(空树)')
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _bounds_center(b):
|
|
113
|
+
# uitest 布局 bounds 兼容两种格式: '[l,t][r,b]'(标准)与 '[l,t,r,b]'
|
|
114
|
+
nums = [int(x) for x in re.findall(r'-?\d+', str(b))]
|
|
115
|
+
if len(nums) != 4:
|
|
116
|
+
return None
|
|
117
|
+
l, t, r, bt = nums
|
|
118
|
+
return (l + r) // 2, (t + bt) // 2
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def cmd_click(dev, a):
|
|
122
|
+
# 1) id 命中 → 取中心坐标(uinput 通道,Toggle 等 hypium touch 失效场景实证可靠)
|
|
123
|
+
for n in walk(dump_layout(dev)):
|
|
124
|
+
t, k, txt, b = attrs(n)
|
|
125
|
+
if k == a.target or txt == a.target:
|
|
126
|
+
c = _bounds_center(b)
|
|
127
|
+
if c:
|
|
128
|
+
print(hdc(dev, ['shell', 'uinput', '-T', '-c', str(c[0]), str(c[1])]).strip() or
|
|
129
|
+
f'clicked {a.target} @ {c}')
|
|
130
|
+
return
|
|
131
|
+
print(f'not found: {a.target}')
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def cmd_tap(dev, a):
|
|
135
|
+
print(hdc(dev, ['shell', 'uinput', '-T', '-c', str(a.x), str(a.y)]).strip() or f'tapped {a.x},{a.y}')
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def cmd_shot(dev, a):
|
|
139
|
+
out = a.path or 'probe_shot.jpeg'
|
|
140
|
+
try:
|
|
141
|
+
from hypium import UiDriver
|
|
142
|
+
UiDriver.connect(device_sn=dev).screenshot(out)
|
|
143
|
+
print(out)
|
|
144
|
+
return
|
|
145
|
+
except Exception:
|
|
146
|
+
pass
|
|
147
|
+
remote = '/data/local/tmp/_probe_shot.jpeg'
|
|
148
|
+
hdc(dev, ['shell', 'snapshot_display', '-f', remote])
|
|
149
|
+
hdc(dev, ['file', 'recv', remote, out])
|
|
150
|
+
hdc(dev, ['shell', 'rm', '-f', remote])
|
|
151
|
+
print(out)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def cmd_hilog(dev, a):
|
|
155
|
+
print(hdc(dev, ['shell', 'hilog', '-z', str(a.n), '-T', a.tag])[:8000])
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def cmd_db_pull(dev, a):
|
|
159
|
+
outdir = 'db_probe'
|
|
160
|
+
os.makedirs(outdir, exist_ok=True)
|
|
161
|
+
listing = hdc(dev, ['shell', f'ls /data/app/el2/*/database/{a.package}/entry/rdb/ 2>/dev/null'])
|
|
162
|
+
got = []
|
|
163
|
+
for fn in [x.strip() for x in listing.splitlines() if x.strip().endswith(('.db', '.db-wal', '.db-shm'))]:
|
|
164
|
+
hdc(dev, ['file', 'recv', f'/data/app/el2/100/database/{a.package}/entry/rdb/{fn}',
|
|
165
|
+
os.path.join(outdir, fn)])
|
|
166
|
+
got.append(fn)
|
|
167
|
+
print(f'pulled {len(got)} files -> {outdir}/: {" ".join(got) or "(空,检查包名)" }')
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def cmd_db_sql(_dev, a):
|
|
171
|
+
r = subprocess.run(['sqlite3', a.db, a.sql], capture_output=True, text=True)
|
|
172
|
+
print(r.stdout or r.stderr)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def main():
|
|
176
|
+
ap = argparse.ArgumentParser(add_help=True)
|
|
177
|
+
ap.add_argument('--device', default=os.environ.get('FORGE_DEVICE', '127.0.0.1:5555'))
|
|
178
|
+
sub = ap.add_subparsers(dest='cmd', required=True)
|
|
179
|
+
sub.add_parser('texts')
|
|
180
|
+
sub.add_parser('ids')
|
|
181
|
+
p = sub.add_parser('find'); p.add_argument('target')
|
|
182
|
+
p = sub.add_parser('tree'); p.add_argument('--grep'); p.add_argument('--depth', type=int, default=6)
|
|
183
|
+
p = sub.add_parser('click'); p.add_argument('target')
|
|
184
|
+
p = sub.add_parser('tap'); p.add_argument('x', type=int); p.add_argument('y', type=int)
|
|
185
|
+
p = sub.add_parser('shot'); p.add_argument('path', nargs='?')
|
|
186
|
+
p = sub.add_parser('hilog'); p.add_argument('tag', nargs='?', default='JSAPP'); p.add_argument('n', nargs='?', type=int, default=400)
|
|
187
|
+
p = sub.add_parser('db-pull'); p.add_argument('package')
|
|
188
|
+
p = sub.add_parser('db-sql'); p.add_argument('db'); p.add_argument('sql')
|
|
189
|
+
a = ap.parse_args()
|
|
190
|
+
dev = a.device
|
|
191
|
+
{'texts': cmd_texts, 'ids': cmd_ids, 'find': cmd_find, 'tree': cmd_tree,
|
|
192
|
+
'click': cmd_click, 'tap': cmd_tap, 'shot': cmd_shot, 'hilog': cmd_hilog,
|
|
193
|
+
'db-pull': cmd_db_pull, 'db-sql': cmd_db_sql}[a.cmd](dev, a)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
if __name__ == '__main__':
|
|
197
|
+
main()
|