hmdev-cli 1.0.3 → 1.0.5
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/package.json +1 -1
- package/python/builder.py +5 -1
- package/python/cli.py +125 -17
- package/scripts/runner.js +2 -2
package/package.json
CHANGED
package/python/builder.py
CHANGED
|
@@ -193,6 +193,10 @@ class HDCTool:
|
|
|
193
193
|
)
|
|
194
194
|
return subprocess.run([self._hdc_path] + args, capture_output=True, text=True, timeout=timeout)
|
|
195
195
|
|
|
196
|
+
@staticmethod
|
|
197
|
+
def succeeded(result: subprocess.CompletedProcess) -> bool:
|
|
198
|
+
return result.returncode == 0 and "[Fail]" not in result.stdout and "[Fail]" not in result.stderr
|
|
199
|
+
|
|
196
200
|
def list_devices(self) -> list[dict]:
|
|
197
201
|
result = self._run(["list", "targets"])
|
|
198
202
|
devices = []
|
|
@@ -221,5 +225,5 @@ class HDCTool:
|
|
|
221
225
|
args.extend(["shell", "aa", "start", "-a", ability, "-b", bundle])
|
|
222
226
|
return self._run(args, timeout=30)
|
|
223
227
|
|
|
224
|
-
def connect_wireless(self, ip_port: str, timeout: int =
|
|
228
|
+
def connect_wireless(self, ip_port: str, timeout: int = 30) -> subprocess.CompletedProcess:
|
|
225
229
|
return self._run(["tconn", ip_port], timeout=timeout)
|
package/python/cli.py
CHANGED
|
@@ -16,6 +16,8 @@ Usage:
|
|
|
16
16
|
import asyncio
|
|
17
17
|
import json
|
|
18
18
|
import re
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
19
21
|
import subprocess
|
|
20
22
|
import time
|
|
21
23
|
from argparse import ArgumentParser, RawDescriptionHelpFormatter, SUPPRESS
|
|
@@ -23,6 +25,8 @@ from html.parser import HTMLParser
|
|
|
23
25
|
from typing import Any
|
|
24
26
|
|
|
25
27
|
import httpx
|
|
28
|
+
from rapidfuzz import fuzz
|
|
29
|
+
import jieba
|
|
26
30
|
|
|
27
31
|
from builder import HvigorTool, HDCTool
|
|
28
32
|
from config import Config
|
|
@@ -293,6 +297,67 @@ async def build_index() -> dict[str, Any]:
|
|
|
293
297
|
return result
|
|
294
298
|
|
|
295
299
|
|
|
300
|
+
# ── Search Ranking ─────────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
def compute_relevance_score(query: str, title: str, object_id: str, catalog_name: str) -> float:
|
|
303
|
+
"""
|
|
304
|
+
Compute a relevance score for a document against the query.
|
|
305
|
+
|
|
306
|
+
Combines:
|
|
307
|
+
- Exact substring/title/ID/category match (highest weight)
|
|
308
|
+
- Fuzzy string matching via rapidfuzz (partial_ratio, token_sort, token_set)
|
|
309
|
+
- Chinese word segmentation overlap via jieba (semantic-like matching)
|
|
310
|
+
- Word prefix bonus
|
|
311
|
+
|
|
312
|
+
Higher score = more relevant.
|
|
313
|
+
"""
|
|
314
|
+
q = query.lower().strip()
|
|
315
|
+
t = title.lower()
|
|
316
|
+
o = object_id.lower()
|
|
317
|
+
c = catalog_name.lower()
|
|
318
|
+
|
|
319
|
+
score = 0.0
|
|
320
|
+
|
|
321
|
+
# ── 1. Exact match (strongest signal) ──
|
|
322
|
+
if t == q:
|
|
323
|
+
score += 5.0
|
|
324
|
+
elif t.startswith(q):
|
|
325
|
+
score += 4.0
|
|
326
|
+
elif q in t:
|
|
327
|
+
score += 3.0
|
|
328
|
+
|
|
329
|
+
if q in o:
|
|
330
|
+
score += 1.5
|
|
331
|
+
if q in c:
|
|
332
|
+
score += 0.5
|
|
333
|
+
|
|
334
|
+
# ── 2. Fuzzy match on title ──
|
|
335
|
+
score += (fuzz.partial_ratio(q, t) / 100.0) * 2.0
|
|
336
|
+
score += (fuzz.token_sort_ratio(q, t) / 100.0) * 1.5
|
|
337
|
+
score += (fuzz.token_set_ratio(q, t) / 100.0) * 1.0
|
|
338
|
+
|
|
339
|
+
# ── 3. Fuzzy match on object_id ──
|
|
340
|
+
score += (fuzz.partial_ratio(q, o) / 100.0) * 0.8
|
|
341
|
+
|
|
342
|
+
# ── 4. Word overlap via jieba (handles Chinese segmentation) ──
|
|
343
|
+
q_words = set(w for w in jieba.lcut(q) if w.strip())
|
|
344
|
+
t_words = set(w for w in jieba.lcut(t) if w.strip())
|
|
345
|
+
if q_words and t_words:
|
|
346
|
+
common = q_words & t_words
|
|
347
|
+
score += (len(common) / len(q_words)) * 2.0
|
|
348
|
+
|
|
349
|
+
# ── 5. Prefix match: query word is prefix of title word ──
|
|
350
|
+
for qw in q_words:
|
|
351
|
+
if len(qw) < 2:
|
|
352
|
+
continue
|
|
353
|
+
for tw in t_words:
|
|
354
|
+
if tw != qw and tw.startswith(qw):
|
|
355
|
+
score += 0.5
|
|
356
|
+
break
|
|
357
|
+
|
|
358
|
+
return score
|
|
359
|
+
|
|
360
|
+
|
|
296
361
|
# ── CLI Helpers ────────────────────────────────────────────────────────────────
|
|
297
362
|
|
|
298
363
|
def parse_doc_url(url: str) -> tuple[str, str]:
|
|
@@ -343,34 +408,58 @@ async def cmd_index(args):
|
|
|
343
408
|
|
|
344
409
|
async def cmd_search(args):
|
|
345
410
|
index = await build_index()
|
|
346
|
-
|
|
347
|
-
|
|
411
|
+
query = args.query.strip()
|
|
412
|
+
if not query:
|
|
413
|
+
print("请提供搜索关键词。")
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
query_lower = query.lower()
|
|
348
417
|
seen = set()
|
|
418
|
+
scored = []
|
|
349
419
|
|
|
350
420
|
for page in index.get("all_pages", []):
|
|
351
|
-
title = page.get("title", "")
|
|
352
|
-
obj_id = page.get("object_id", "")
|
|
353
|
-
|
|
421
|
+
title = page.get("title", "")
|
|
422
|
+
obj_id = page.get("object_id", "")
|
|
423
|
+
catalog = page.get("catalog_name", "")
|
|
424
|
+
|
|
425
|
+
score = compute_relevance_score(query, title, obj_id, catalog)
|
|
426
|
+
|
|
427
|
+
# Include if exact match exists or fuzzy score is significant
|
|
428
|
+
if (query_lower in title.lower()
|
|
429
|
+
or query_lower in obj_id.lower()
|
|
430
|
+
or query_lower in catalog.lower()
|
|
431
|
+
or score >= 1.5):
|
|
354
432
|
if page.get("url") not in seen:
|
|
355
433
|
seen.add(page["url"])
|
|
356
|
-
|
|
434
|
+
page = dict(page)
|
|
435
|
+
page["_score"] = round(score, 2)
|
|
436
|
+
scored.append(page)
|
|
437
|
+
|
|
438
|
+
# Sort: higher score first, shorter title as tiebreaker
|
|
439
|
+
scored.sort(key=lambda p: (-p["_score"], len(p.get("title", ""))))
|
|
357
440
|
|
|
358
441
|
if args.json:
|
|
359
|
-
|
|
442
|
+
out = {
|
|
443
|
+
"query": args.query,
|
|
444
|
+
"total": len(scored),
|
|
445
|
+
"results": scored[:50],
|
|
446
|
+
}
|
|
447
|
+
print_json(out)
|
|
360
448
|
return
|
|
361
449
|
|
|
362
|
-
if not
|
|
450
|
+
if not scored:
|
|
363
451
|
print(f"未找到与 '{args.query}' 相关的文档。")
|
|
364
452
|
print(f"可用分类: {', '.join(f'{v}({k})' for k, v in CATALOGS.items())}")
|
|
365
453
|
return
|
|
366
454
|
|
|
367
|
-
print(f"搜索结果: '{args.query}' (共 {len(
|
|
368
|
-
for page in
|
|
455
|
+
print(f"搜索结果: '{args.query}' (共 {len(scored)} 篇)\n")
|
|
456
|
+
for page in scored[:30]:
|
|
369
457
|
cat = CATALOGS.get(page.get("catalog_name", ""), page.get("catalog_name", ""))
|
|
370
|
-
|
|
371
|
-
print(f"
|
|
372
|
-
|
|
373
|
-
|
|
458
|
+
bar = "█" * min(int(page["_score"]), 10) + "░" * (10 - min(int(page["_score"]), 10))
|
|
459
|
+
print(f" {bar} [{cat}] {page['title']}")
|
|
460
|
+
print(f" {page['url']}")
|
|
461
|
+
if len(scored) > 30:
|
|
462
|
+
print(f"\n...及另外 {len(scored) - 30} 篇")
|
|
374
463
|
|
|
375
464
|
|
|
376
465
|
async def cmd_get(args):
|
|
@@ -477,7 +566,7 @@ async def cmd_deploy(args):
|
|
|
477
566
|
if args.tconn:
|
|
478
567
|
print(f"[hmdev] 无线连接: {args.tconn}")
|
|
479
568
|
tr = hdc.connect_wireless(args.tconn)
|
|
480
|
-
if tr
|
|
569
|
+
if not HDCTool.succeeded(tr):
|
|
481
570
|
print(f"[hmdev] ❌ 无线连接失败: {tr.stderr.strip()}")
|
|
482
571
|
return
|
|
483
572
|
print(f"[hmdev] ✅ 无线连接成功")
|
|
@@ -493,7 +582,7 @@ async def cmd_deploy(args):
|
|
|
493
582
|
|
|
494
583
|
print(f"[hmdev] 正在安装: {hap_path}")
|
|
495
584
|
ir = hdc.install_hap(hap_path, device_id)
|
|
496
|
-
if ir
|
|
585
|
+
if not HDCTool.succeeded(ir):
|
|
497
586
|
err = ir.stderr.strip() or ir.stdout.strip()
|
|
498
587
|
print(f"[hmdev] ❌ 安装失败: {err}")
|
|
499
588
|
return
|
|
@@ -507,7 +596,7 @@ async def cmd_deploy(args):
|
|
|
507
596
|
else:
|
|
508
597
|
print(f"[hmdev] 正在启动: {bundle}")
|
|
509
598
|
sr = hdc.start_app(bundle, args.ability, device_id)
|
|
510
|
-
if sr
|
|
599
|
+
if not HDCTool.succeeded(sr):
|
|
511
600
|
print(f"[hmdev] ❌ 启动失败: {sr.stderr.strip()}")
|
|
512
601
|
else:
|
|
513
602
|
print(f"[hmdev] ✅ 应用已启动")
|
|
@@ -668,6 +757,20 @@ async def cmd_config(args):
|
|
|
668
757
|
print()
|
|
669
758
|
|
|
670
759
|
|
|
760
|
+
async def cmd_update(args):
|
|
761
|
+
npm = shutil.which("npm") or shutil.which("npm.cmd")
|
|
762
|
+
if not npm:
|
|
763
|
+
print("[hmdev] ❌ 未找到 npm。请确保 Node.js 已安装。")
|
|
764
|
+
return
|
|
765
|
+
print("[hmdev] 正在更新 hmdev-cli ...")
|
|
766
|
+
result = subprocess.run([npm, "update", "-g", "hmdev-cli"], capture_output=True, text=True)
|
|
767
|
+
if result.returncode == 0:
|
|
768
|
+
print("[hmdev] ✅ 更新成功")
|
|
769
|
+
else:
|
|
770
|
+
err = result.stderr.strip() or result.stdout.strip()
|
|
771
|
+
print(f"[hmdev] ❌ 更新失败: {err}")
|
|
772
|
+
|
|
773
|
+
|
|
671
774
|
# ── CLI Registration ──────────────────────────────────────────────────────────
|
|
672
775
|
|
|
673
776
|
CLI_COMMANDS = {
|
|
@@ -682,6 +785,7 @@ CLI_COMMANDS = {
|
|
|
682
785
|
"run": cmd_run,
|
|
683
786
|
"connect": cmd_connect,
|
|
684
787
|
"config": cmd_config,
|
|
788
|
+
"update": cmd_update,
|
|
685
789
|
}
|
|
686
790
|
|
|
687
791
|
|
|
@@ -765,6 +869,10 @@ def build_cli_parser():
|
|
|
765
869
|
p.add_argument("--reset", metavar="KEY", help="重置配置项")
|
|
766
870
|
p.add_argument("--json", action="store_true", dest="json", help=SUPPRESS)
|
|
767
871
|
|
|
872
|
+
# update
|
|
873
|
+
p = sub.add_parser("update", help="更新 hmdev-cli 到最新版本 (npm update -g)")
|
|
874
|
+
p.add_argument("--json", action="store_true", dest="json", help=SUPPRESS)
|
|
875
|
+
|
|
768
876
|
return parser
|
|
769
877
|
|
|
770
878
|
|
package/scripts/runner.js
CHANGED
|
@@ -102,8 +102,8 @@ async function ensurePython() {
|
|
|
102
102
|
});
|
|
103
103
|
} catch { /* non-fatal */ }
|
|
104
104
|
|
|
105
|
-
console.error(`${TAG} 正在安装 Python 依赖 (httpx)...`);
|
|
106
|
-
execSync(`"${getVenvPip()}" install httpx`, {
|
|
105
|
+
console.error(`${TAG} 正在安装 Python 依赖 (httpx, rapidfuzz, jieba)...`);
|
|
106
|
+
execSync(`"${getVenvPip()}" install httpx rapidfuzz jieba`, {
|
|
107
107
|
stdio: 'pipe',
|
|
108
108
|
timeout: 120000,
|
|
109
109
|
env: cleanEnv,
|