@harmonyos-arkts/opencode-plugin 0.0.10 → 0.0.12
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 +1 -1
- package/dist/index.js +449 -136
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
- **HarmonyOS设计专家**:为HarmonyOS应用和原子化服务创建详细的PRD设计文档
|
|
9
9
|
- **HarmonyOS开发专家**:根据设计文档遵循HarmonyOS规范实现功能
|
|
10
10
|
- **HarmonyOS构建**:使用harmonyos-hvigor技能构建项目并确保编译成功
|
|
11
|
-
- **HarmonyOS文档查询**:通过 `harmony-doc
|
|
11
|
+
- **HarmonyOS文档查询**:通过 `harmony-doc` 工具搜索和查看华为开发者文档(含官方文档与社区内容),支持关键词搜索和分页查看全文
|
|
12
12
|
|
|
13
13
|
## 快速开始
|
|
14
14
|
|
package/dist/index.js
CHANGED
|
@@ -45333,58 +45333,49 @@ function convert_search_result(searchResults, prefix) {
|
|
|
45333
45333
|
});
|
|
45334
45334
|
}
|
|
45335
45335
|
|
|
45336
|
-
// src/tools/skill-search/skill-search-tool.ts
|
|
45337
|
-
function skillSearchTool(managers) {
|
|
45338
|
-
return tool({
|
|
45339
|
-
description: "Search for relevant documents within the harmonyos-atomic-dev skill directory by keywords. Returns the top K most relevant document snippets from the skill directory, ranked by keyword match frequency. Use this tool instead of Glob/Grep when you need to find specific knowledge or documentation within harmonyos-atomic-dev skill. The results include experience_file_path (path to the matching experience document best practices) and ets_file_path (path to the ETS code examples and SDK API file). IMPORTANT: Each scenario only supports ONE tool call. Do NOT call this tool multiple times.",
|
|
45340
|
-
args: {
|
|
45341
|
-
skill_path: tool.schema.string("Absolute path to the harmonyos-atomic-dev skill directory. IMPORTANT: this path should not contain any prefix or suffix like 'file://' or 'SKILL.md'."),
|
|
45342
|
-
query: tool.schema.string("A decomposed requirement or intent describing what you want to find, broken down into searchable keywords separated by spaces. QUERY TIPS: For best results, decompose your intent into short space-separated keywords instead of long sentences. Include: 1) Kit or component name (e.g. ScanKit, AdsKit, ShareKit), 2) specific API or method names (e.g. scanBarcode, loadAd, ShareController), 3) feature keywords (e.g. \u626B\u7801, \u5E7F\u544A\u52A0\u8F7D, \u5206\u4EAB). Example: 'ScanKit \u626B\u7801 scanBarcode startScanForResult' instead of '\u5E2E\u6211\u5B9E\u73B0\u4E00\u4E2A\u626B\u7801\u529F\u80FD'. "),
|
|
45343
|
-
topK: tool.schema.number("Maximum number of top-ranked documents to return. Actual results may be fewer depending on query relevance.").min(1).max(3).default(3)
|
|
45344
|
-
},
|
|
45345
|
-
execute: async (args, context) => {
|
|
45346
|
-
try {
|
|
45347
|
-
const results = await searchSkill(args.skill_path, args.query, args.topK);
|
|
45348
|
-
if (results.length === 0) {
|
|
45349
|
-
return "No relative items found in the skill directory.";
|
|
45350
|
-
}
|
|
45351
|
-
const sdkInfo = results.map((r) => {
|
|
45352
|
-
const parts = [];
|
|
45353
|
-
if (r.experience_file_path) parts.push(`experience path ${r.experience_file_path}`);
|
|
45354
|
-
if (r.ets_file_path) parts.push(`sample code path ${r.ets_file_path}`);
|
|
45355
|
-
if (r.sdk_file_path) parts.push(`sdk info path ${r.sdk_file_path}`);
|
|
45356
|
-
return parts.join("\n");
|
|
45357
|
-
}).join("\n\n");
|
|
45358
|
-
return `You can read the following files as needed to obtain information.
|
|
45359
|
-
${sdkInfo}`;
|
|
45360
|
-
} catch (e) {
|
|
45361
|
-
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
45362
|
-
}
|
|
45363
|
-
}
|
|
45364
|
-
});
|
|
45365
|
-
}
|
|
45366
|
-
|
|
45367
45336
|
// src/tools/harmony-doc/api-client.ts
|
|
45368
45337
|
var BASE_URL = "https://svc-drcn.developer.huawei.com/community/servlet";
|
|
45369
45338
|
var HEADERS = {
|
|
45370
45339
|
"Content-Type": "application/json",
|
|
45371
|
-
|
|
45340
|
+
Referer: "https://developer.huawei.com/consumer/cn/doc/",
|
|
45372
45341
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
|
|
45373
45342
|
};
|
|
45343
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
45374
45344
|
async function post(path7, body) {
|
|
45375
|
-
const
|
|
45376
|
-
|
|
45377
|
-
|
|
45378
|
-
|
|
45379
|
-
|
|
45380
|
-
|
|
45381
|
-
|
|
45382
|
-
|
|
45383
|
-
|
|
45384
|
-
|
|
45385
|
-
|
|
45345
|
+
const controller = new AbortController();
|
|
45346
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
45347
|
+
try {
|
|
45348
|
+
const res = await fetch(BASE_URL + path7, {
|
|
45349
|
+
method: "POST",
|
|
45350
|
+
headers: HEADERS,
|
|
45351
|
+
body: JSON.stringify(body),
|
|
45352
|
+
signal: controller.signal
|
|
45353
|
+
});
|
|
45354
|
+
if (!res.ok) {
|
|
45355
|
+
throw new Error(`API request failed: ${res.status} ${res.statusText} for ${path7}`);
|
|
45356
|
+
}
|
|
45357
|
+
const data = await res.json();
|
|
45358
|
+
if (data.code && data.code !== 0 && data.code !== "0") {
|
|
45359
|
+
throw new Error(`API error [${data.code}]: ${data.message} for ${path7}`);
|
|
45360
|
+
}
|
|
45361
|
+
return data;
|
|
45362
|
+
} finally {
|
|
45363
|
+
clearTimeout(timer);
|
|
45386
45364
|
}
|
|
45387
|
-
|
|
45365
|
+
}
|
|
45366
|
+
var CATALOG_DISPLAY_NAMES = {
|
|
45367
|
+
"harmonyos-guides": "\u6307\u5357",
|
|
45368
|
+
"harmonyos-references": "API\u53C2\u8003",
|
|
45369
|
+
"harmonyos-best-practices": "\u6700\u4F73\u5B9E\u8DF5",
|
|
45370
|
+
"harmonyos-faqs": "FAQ",
|
|
45371
|
+
"design-guides": "\u8BBE\u8BA1\u6307\u5357",
|
|
45372
|
+
"atomic-guides": "\u6307\u5357",
|
|
45373
|
+
"atomic-references": "API\u53C2\u8003",
|
|
45374
|
+
"atomic-faqs": "FAQ",
|
|
45375
|
+
"atomic-ascf": "ASCF\u6846\u67B6"
|
|
45376
|
+
};
|
|
45377
|
+
function catalogDisplayName(catalogName) {
|
|
45378
|
+
return CATALOG_DISPLAY_NAMES[catalogName] || "";
|
|
45388
45379
|
}
|
|
45389
45380
|
async function searchDocs(keyword, maxResults = 10) {
|
|
45390
45381
|
const data = await post(
|
|
@@ -45405,12 +45396,29 @@ async function searchDocs(keyword, maxResults = 10) {
|
|
|
45405
45396
|
}
|
|
45406
45397
|
);
|
|
45407
45398
|
return (data.resultList ?? []).map((r) => {
|
|
45399
|
+
let subsection = "";
|
|
45400
|
+
let anchorId = "";
|
|
45401
|
+
if (r.anchorHighlightInfo) {
|
|
45402
|
+
subsection = r.anchorHighlightInfo.replace(/<\/?em>/g, "");
|
|
45403
|
+
}
|
|
45408
45404
|
let breadcrumb = [];
|
|
45409
45405
|
try {
|
|
45410
45406
|
const ext = typeof r.metaData?.ext === "string" ? JSON.parse(r.metaData.ext) : r.metaData?.ext ?? {};
|
|
45411
45407
|
const nodeNames = Array.isArray(ext.nodeNames) ? ext.nodeNames : [];
|
|
45408
|
+
const catalogName = typeof ext.catalogName === "string" ? ext.catalogName : "";
|
|
45412
45409
|
if (nodeNames.length > 0) {
|
|
45413
|
-
|
|
45410
|
+
const rootName = catalogDisplayName(catalogName) || catalogName;
|
|
45411
|
+
breadcrumb = rootName ? [rootName, ...nodeNames] : nodeNames;
|
|
45412
|
+
}
|
|
45413
|
+
if (subsection) {
|
|
45414
|
+
try {
|
|
45415
|
+
const anchors = typeof ext.anchorList === "string" ? JSON.parse(ext.anchorList) : ext.anchorList;
|
|
45416
|
+
if (Array.isArray(anchors)) {
|
|
45417
|
+
const match = anchors.find((a) => a.title === subsection);
|
|
45418
|
+
if (match) anchorId = match.anchorId ?? "";
|
|
45419
|
+
}
|
|
45420
|
+
} catch {
|
|
45421
|
+
}
|
|
45414
45422
|
}
|
|
45415
45423
|
} catch {
|
|
45416
45424
|
}
|
|
@@ -45418,17 +45426,34 @@ async function searchDocs(keyword, maxResults = 10) {
|
|
|
45418
45426
|
title: r.name ?? "",
|
|
45419
45427
|
url: r.url ?? "",
|
|
45420
45428
|
excerpt: (r.content ?? "").replace(/\n/g, " ").substring(0, 200),
|
|
45429
|
+
excerptTruncated: (r.content ?? "").replace(/\n/g, " ").length > 200,
|
|
45421
45430
|
breadcrumb,
|
|
45422
45431
|
contentType: r.metaData?.type ?? 0,
|
|
45423
|
-
timestamp: r.metaData?.timestamp ?? ""
|
|
45432
|
+
timestamp: r.metaData?.timestamp ?? "",
|
|
45433
|
+
subsection,
|
|
45434
|
+
anchorId
|
|
45424
45435
|
};
|
|
45425
45436
|
});
|
|
45426
45437
|
}
|
|
45427
45438
|
function objectIdFromUrl(url3) {
|
|
45428
45439
|
try {
|
|
45429
|
-
const
|
|
45440
|
+
const parsed = new URL(url3);
|
|
45441
|
+
const pathname = parsed.pathname.replace(/\/$/, "");
|
|
45442
|
+
const queryId = parsed.searchParams.get("objectId") || parsed.searchParams.get("id");
|
|
45443
|
+
if (queryId) return queryId;
|
|
45430
45444
|
const parts = pathname.split("/");
|
|
45431
|
-
|
|
45445
|
+
const lastSegment = parts[parts.length - 1];
|
|
45446
|
+
if (lastSegment && lastSegment.length > 2) return lastSegment;
|
|
45447
|
+
if (parts.length >= 2) {
|
|
45448
|
+
const secondLast = parts[parts.length - 2];
|
|
45449
|
+
if (secondLast && secondLast.length > 2) return secondLast;
|
|
45450
|
+
}
|
|
45451
|
+
const hash3 = parsed.hash;
|
|
45452
|
+
if (hash3) {
|
|
45453
|
+
const hashId = hash3.replace(/^#/, "");
|
|
45454
|
+
if (hashId.length > 5) return hashId;
|
|
45455
|
+
}
|
|
45456
|
+
return null;
|
|
45432
45457
|
} catch {
|
|
45433
45458
|
return null;
|
|
45434
45459
|
}
|
|
@@ -45442,48 +45467,148 @@ async function fetchDocument(objectId, language = "cn") {
|
|
|
45442
45467
|
if (!v) {
|
|
45443
45468
|
throw new Error(`Empty response for objectId: ${objectId}`);
|
|
45444
45469
|
}
|
|
45470
|
+
const html = v.content?.content ?? "";
|
|
45445
45471
|
return {
|
|
45446
45472
|
title: v.title ?? "",
|
|
45447
|
-
html
|
|
45473
|
+
html,
|
|
45448
45474
|
objectId,
|
|
45449
45475
|
catalogName: v.catalogName ?? "",
|
|
45450
45476
|
displayUpdateTime: v.displayUpdateTime ?? ""
|
|
45451
45477
|
};
|
|
45452
45478
|
}
|
|
45479
|
+
var treeCache = /* @__PURE__ */ new Map();
|
|
45480
|
+
async function getCatalogTree(language, catalogName, objectId) {
|
|
45481
|
+
const body = { language, catalogName };
|
|
45482
|
+
if (objectId) body.objectId = objectId;
|
|
45483
|
+
const data = await post(
|
|
45484
|
+
"/consumer/cn/documentPortal/getCatalogTree",
|
|
45485
|
+
body
|
|
45486
|
+
);
|
|
45487
|
+
return data.value?.catalogTreeList ?? [];
|
|
45488
|
+
}
|
|
45489
|
+
async function resolveBreadcrumb(language, catalogName, relateDocument) {
|
|
45490
|
+
if (!relateDocument) return [];
|
|
45491
|
+
const cacheKey = `${language}:${catalogName}`;
|
|
45492
|
+
if (!treeCache.has(cacheKey)) {
|
|
45493
|
+
const nodes = await getCatalogTree(language, catalogName);
|
|
45494
|
+
const map4 = /* @__PURE__ */ new Map();
|
|
45495
|
+
const walk = (list) => {
|
|
45496
|
+
for (const node of list) {
|
|
45497
|
+
map4.set(node.nodeId, { name: node.nodeName, parent: node.parent, rel: node.relateDocument });
|
|
45498
|
+
if (node.children?.length) walk(node.children);
|
|
45499
|
+
}
|
|
45500
|
+
};
|
|
45501
|
+
walk(nodes);
|
|
45502
|
+
treeCache.set(cacheKey, map4);
|
|
45503
|
+
}
|
|
45504
|
+
const map3 = treeCache.get(cacheKey);
|
|
45505
|
+
let target;
|
|
45506
|
+
for (const node of map3.values()) {
|
|
45507
|
+
if (node.rel === relateDocument) {
|
|
45508
|
+
target = node;
|
|
45509
|
+
break;
|
|
45510
|
+
}
|
|
45511
|
+
}
|
|
45512
|
+
if (!target) return [];
|
|
45513
|
+
const breadcrumb = [];
|
|
45514
|
+
let current = target;
|
|
45515
|
+
while (current) {
|
|
45516
|
+
breadcrumb.unshift(current.name);
|
|
45517
|
+
current = current.parent ? map3.get(current.parent) : void 0;
|
|
45518
|
+
}
|
|
45519
|
+
const rootName = catalogDisplayName(catalogName);
|
|
45520
|
+
if (rootName && breadcrumb[0] !== rootName) {
|
|
45521
|
+
breadcrumb.unshift(rootName);
|
|
45522
|
+
}
|
|
45523
|
+
return breadcrumb;
|
|
45524
|
+
}
|
|
45525
|
+
function formatUpdateTime(utcTimeStr) {
|
|
45526
|
+
if (!utcTimeStr) return "";
|
|
45527
|
+
const dt = /* @__PURE__ */ new Date(utcTimeStr.replace(" ", "T") + "Z");
|
|
45528
|
+
if (isNaN(dt.getTime())) return utcTimeStr;
|
|
45529
|
+
const bj = new Date(dt.getTime() + 8 * 60 * 60 * 1e3);
|
|
45530
|
+
const y = bj.getUTCFullYear();
|
|
45531
|
+
const m = String(bj.getUTCMonth() + 1).padStart(2, "0");
|
|
45532
|
+
const d = String(bj.getUTCDate()).padStart(2, "0");
|
|
45533
|
+
const h = String(bj.getUTCHours()).padStart(2, "0");
|
|
45534
|
+
const min = String(bj.getUTCMinutes()).padStart(2, "0");
|
|
45535
|
+
return `${y}-${m}-${d} ${h}:${min}`;
|
|
45536
|
+
}
|
|
45453
45537
|
|
|
45454
|
-
// src/tools/
|
|
45455
|
-
function
|
|
45538
|
+
// src/tools/skill-search/skill-search-tool.ts
|
|
45539
|
+
function skillSearchTool(managers) {
|
|
45456
45540
|
return tool({
|
|
45457
|
-
description: "Search HarmonyOS
|
|
45541
|
+
description: "Search HarmonyOS development knowledge across BOTH local skill documents AND official Huawei documentation. Returns two sections: (1) Local Skill Results \u2014 file paths to curated code examples, best practices, and SDK API info from the harmonyos-atomic-dev skill directory; (2) Official Documentation \u2014 search results from developer.huawei.com with titles, URLs, breadcrumbs, and excerpts. Use this tool for any HarmonyOS API, component, Kit, or development pattern lookup. After reviewing results, read local skill files for code patterns and use harmony-doc-view to read full official documents by URL. IMPORTANT: This tool already searches official documentation \u2014 do NOT also call harmony-doc-view for the same purpose. Only use harmony-doc-view to read a specific document page from the URLs listed below. Each scenario only supports ONE tool call. Do NOT call this tool multiple times.",
|
|
45458
45542
|
args: {
|
|
45459
|
-
|
|
45460
|
-
|
|
45543
|
+
skill_path: tool.schema.string("Absolute path to the harmonyos-atomic-dev skill directory. IMPORTANT: this path should not contain any prefix or suffix like 'file://' or 'SKILL.md'."),
|
|
45544
|
+
query: tool.schema.string("A decomposed requirement or intent describing what you want to find, broken down into searchable keywords separated by spaces. QUERY TIPS: For best results, decompose your intent into short space-separated keywords instead of long sentences. Include: 1) Kit or component name (e.g. ScanKit, AdsKit, ShareKit), 2) specific API or method names (e.g. scanBarcode, loadAd, ShareController), 3) feature keywords (e.g. \u626B\u7801, \u5E7F\u544A\u52A0\u8F7D, \u5206\u4EAB). Example: 'ScanKit \u626B\u7801 scanBarcode startScanForResult' instead of '\u5E2E\u6211\u5B9E\u73B0\u4E00\u4E2A\u626B\u7801\u529F\u80FD'. "),
|
|
45545
|
+
topK: tool.schema.number("Maximum number of top-ranked documents to return. Actual results may be fewer depending on query relevance.").min(1).max(3).default(3)
|
|
45461
45546
|
},
|
|
45462
|
-
execute: async (args,
|
|
45463
|
-
|
|
45464
|
-
|
|
45465
|
-
|
|
45466
|
-
|
|
45467
|
-
|
|
45468
|
-
|
|
45469
|
-
|
|
45470
|
-
|
|
45471
|
-
|
|
45472
|
-
|
|
45473
|
-
|
|
45474
|
-
|
|
45475
|
-
|
|
45547
|
+
execute: async (args, context) => {
|
|
45548
|
+
const [localResults, docResults] = await Promise.all([
|
|
45549
|
+
searchSkill(args.skill_path, args.query, args.topK).catch(
|
|
45550
|
+
(e) => new Error(`Local search failed: ${e instanceof Error ? e.message : String(e)}`)
|
|
45551
|
+
),
|
|
45552
|
+
searchDocs(args.query, 5).catch(
|
|
45553
|
+
(e) => new Error(`Doc search failed: ${e instanceof Error ? e.message : String(e)}`)
|
|
45554
|
+
)
|
|
45555
|
+
]);
|
|
45556
|
+
const parts = [];
|
|
45557
|
+
if (localResults instanceof Error) {
|
|
45558
|
+
parts.push(`## Local Skill Results
|
|
45559
|
+
|
|
45560
|
+
${localResults.message}`);
|
|
45561
|
+
} else if (localResults.length === 0) {
|
|
45562
|
+
parts.push("## Local Skill Results\n\nNo matching skill documents found.");
|
|
45563
|
+
} else {
|
|
45564
|
+
const localInfo = localResults.map((r) => {
|
|
45565
|
+
const lines = [];
|
|
45566
|
+
if (r.experience_file_path) lines.push(`- experience path: ${r.experience_file_path}`);
|
|
45567
|
+
if (r.ets_file_path) lines.push(`- sample code path: ${r.ets_file_path}`);
|
|
45568
|
+
if (r.sdk_file_path) lines.push(`- sdk info path: ${r.sdk_file_path}`);
|
|
45569
|
+
return lines.join("\n");
|
|
45570
|
+
}).join("\n\n");
|
|
45571
|
+
parts.push(`## Local Skill Results
|
|
45572
|
+
|
|
45573
|
+
${localInfo}`);
|
|
45574
|
+
}
|
|
45575
|
+
if (docResults instanceof Error) {
|
|
45576
|
+
parts.push(`## Official Documentation
|
|
45577
|
+
|
|
45578
|
+
${docResults.message}`);
|
|
45579
|
+
} else if (docResults.length === 0) {
|
|
45580
|
+
parts.push("## Official Documentation\n\nNo matching official documents found.");
|
|
45581
|
+
} else {
|
|
45582
|
+
const docInfo = docResults.map((r, i) => {
|
|
45583
|
+
const lines = [];
|
|
45584
|
+
let title = r.title;
|
|
45585
|
+
if (r.subsection && r.subsection !== r.title) {
|
|
45586
|
+
title += ` #${r.subsection}`;
|
|
45587
|
+
}
|
|
45588
|
+
lines.push(`${i + 1}. **${title}**`);
|
|
45589
|
+
let displayUrl = r.url;
|
|
45590
|
+
if (r.anchorId) {
|
|
45591
|
+
displayUrl += `#${r.anchorId}`;
|
|
45592
|
+
}
|
|
45593
|
+
lines.push(` URL: ${displayUrl}`);
|
|
45476
45594
|
if (r.excerpt) {
|
|
45477
|
-
|
|
45595
|
+
const suffix = r.excerptTruncated ? "..." : "";
|
|
45596
|
+
lines.push(` ${r.excerpt}${suffix}`);
|
|
45597
|
+
}
|
|
45598
|
+
if (r.breadcrumb.length > 0) {
|
|
45599
|
+
lines.push(` \u6765\u81EA: ${r.breadcrumb.join(" > ")}`);
|
|
45478
45600
|
}
|
|
45479
|
-
return
|
|
45601
|
+
return lines.join("\n");
|
|
45480
45602
|
}).join("\n\n");
|
|
45481
|
-
|
|
45603
|
+
parts.push(
|
|
45604
|
+
`## Official Documentation
|
|
45482
45605
|
|
|
45483
|
-
${
|
|
45484
|
-
|
|
45485
|
-
|
|
45606
|
+
${docInfo}
|
|
45607
|
+
|
|
45608
|
+
Use harmony-doc-view with a URL above to read any official document in full.`
|
|
45609
|
+
);
|
|
45486
45610
|
}
|
|
45611
|
+
return parts.join("\n\n");
|
|
45487
45612
|
}
|
|
45488
45613
|
});
|
|
45489
45614
|
}
|
|
@@ -46176,18 +46301,61 @@ var removeUIParagraphs = {
|
|
|
46176
46301
|
return "";
|
|
46177
46302
|
}
|
|
46178
46303
|
};
|
|
46304
|
+
var NAV_SHORT_TEXT = /* @__PURE__ */ new Set([
|
|
46305
|
+
"\u7B80\u4F53\u4E2D\u6587",
|
|
46306
|
+
"English",
|
|
46307
|
+
"\u4E0B\u8F7D App",
|
|
46308
|
+
"\u63A2\u7D22",
|
|
46309
|
+
"\u8BBE\u8BA1",
|
|
46310
|
+
"\u5F00\u53D1",
|
|
46311
|
+
"\u5206\u53D1",
|
|
46312
|
+
"\u63A8\u5E7F\u4E0E\u53D8\u73B0",
|
|
46313
|
+
"\u751F\u6001\u5408\u4F5C",
|
|
46314
|
+
"\u652F\u6301",
|
|
46315
|
+
"\u66F4\u591A",
|
|
46316
|
+
"\u7ACB\u5373\u767B\u5F55",
|
|
46317
|
+
"\u8F93\u5165\u5173\u952E\u5B57\u641C\u7D22",
|
|
46318
|
+
"Hello\uFF0C",
|
|
46319
|
+
"\u6B22\u8FCE\u6765\u5230\u5F00\u53D1\u8005\u8054\u76DF",
|
|
46320
|
+
"CTRL+K",
|
|
46321
|
+
"Created with Pixso",
|
|
46322
|
+
"\u7248\u672C\u8BF4\u660E",
|
|
46323
|
+
"\u6307\u5357",
|
|
46324
|
+
"API\u53C2\u8003",
|
|
46325
|
+
"\u6700\u4F73\u5B9E\u8DF5",
|
|
46326
|
+
"FAQ",
|
|
46327
|
+
"\u53D8\u66F4\u9884\u544A",
|
|
46328
|
+
"\u591A\u8BBE\u5907\u573A\u666F",
|
|
46329
|
+
"\u624B\u673A",
|
|
46330
|
+
"\u5E94\u7528\u8D28\u91CF",
|
|
46331
|
+
"\u6280\u672F\u8D28\u91CF",
|
|
46332
|
+
"\u5F00\u53D1\u8005\u80FD\u529B\u8BA4\u8BC1",
|
|
46333
|
+
"\u6211\u7684",
|
|
46334
|
+
"\u7BA1\u7406\u4E2D\u5FC3",
|
|
46335
|
+
"\u4E2A\u4EBA\u4E2D\u5FC3",
|
|
46336
|
+
"\u6211\u7684\u5B66\u5802",
|
|
46337
|
+
"\u6211\u7684\u6536\u85CF",
|
|
46338
|
+
"\u6211\u7684\u6D3B\u52A8",
|
|
46339
|
+
"\u6211\u7684\u5DE5\u5355"
|
|
46340
|
+
]);
|
|
46179
46341
|
var removeNavigation = {
|
|
46180
46342
|
filter(node) {
|
|
46181
46343
|
if (node.nodeType !== 1) return false;
|
|
46344
|
+
const text = (node.textContent ?? "").trim();
|
|
46345
|
+
if (text.length <= 20 && NAV_SHORT_TEXT.has(text)) {
|
|
46346
|
+
return true;
|
|
46347
|
+
}
|
|
46348
|
+
if (node.nodeName === "P" && text.length === 0) {
|
|
46349
|
+
const hasContent = node.querySelector("img, code, pre, table");
|
|
46350
|
+
if (!hasContent) return true;
|
|
46351
|
+
}
|
|
46182
46352
|
const cls = node.className?.toLowerCase() ?? "";
|
|
46183
46353
|
const id = node.id?.toLowerCase() ?? "";
|
|
46184
46354
|
const unwantedClasses = [
|
|
46185
46355
|
"top-bar",
|
|
46186
46356
|
"menu-bar",
|
|
46187
|
-
"sidebar",
|
|
46188
46357
|
"search-bar",
|
|
46189
46358
|
"breadcrumb",
|
|
46190
|
-
"toolbar",
|
|
46191
46359
|
"footer-nav",
|
|
46192
46360
|
"main-nav",
|
|
46193
46361
|
"side-nav",
|
|
@@ -46204,7 +46372,29 @@ var removeNavigation = {
|
|
|
46204
46372
|
"feedback-section",
|
|
46205
46373
|
"qrcode-section"
|
|
46206
46374
|
];
|
|
46207
|
-
|
|
46375
|
+
const classList = cls.split(/\s+/);
|
|
46376
|
+
const matchesClass = classList.some(
|
|
46377
|
+
(c) => unwantedClasses.some((u) => c === u || c.startsWith(u + "-") || c.endsWith("-" + u))
|
|
46378
|
+
);
|
|
46379
|
+
const matchesId = unwantedClasses.some(
|
|
46380
|
+
(u) => id === u || id.startsWith(u + "-") || id.endsWith("-" + u)
|
|
46381
|
+
);
|
|
46382
|
+
return matchesClass || matchesId;
|
|
46383
|
+
},
|
|
46384
|
+
replacement() {
|
|
46385
|
+
return "";
|
|
46386
|
+
}
|
|
46387
|
+
};
|
|
46388
|
+
var removeCodeUI = {
|
|
46389
|
+
filter(node) {
|
|
46390
|
+
if (node.nodeType === 3) {
|
|
46391
|
+
return isUnwantedText(node.textContent ?? "");
|
|
46392
|
+
}
|
|
46393
|
+
if (node.nodeType === 1 && ["P", "DIV", "SPAN"].includes(node.nodeName)) {
|
|
46394
|
+
const text = (node.textContent ?? "").trim();
|
|
46395
|
+
return isUnwantedText(text);
|
|
46396
|
+
}
|
|
46397
|
+
return false;
|
|
46208
46398
|
},
|
|
46209
46399
|
replacement() {
|
|
46210
46400
|
return "";
|
|
@@ -46240,6 +46430,18 @@ var huaweiCodeBlock = {
|
|
|
46240
46430
|
return "\n```" + language + "\n" + lines.join("\n") + "\n```\n";
|
|
46241
46431
|
}
|
|
46242
46432
|
};
|
|
46433
|
+
function inferLanguage(codeText) {
|
|
46434
|
+
if (codeText.includes("import ") || codeText.includes("export ") || codeText.includes("interface ")) {
|
|
46435
|
+
return "typescript";
|
|
46436
|
+
}
|
|
46437
|
+
if (codeText.includes("#include") || codeText.includes("int main")) {
|
|
46438
|
+
return "cpp";
|
|
46439
|
+
}
|
|
46440
|
+
if (codeText.includes("public class")) {
|
|
46441
|
+
return "java";
|
|
46442
|
+
}
|
|
46443
|
+
return "";
|
|
46444
|
+
}
|
|
46243
46445
|
var genericPre = {
|
|
46244
46446
|
filter(node) {
|
|
46245
46447
|
return node.nodeName === "PRE" && !node.querySelector("ol.linenums");
|
|
@@ -46257,6 +46459,9 @@ var genericPre = {
|
|
|
46257
46459
|
const codeMatch = (codeElem.className ?? "").match(/language-(\w+)/);
|
|
46258
46460
|
if (codeMatch) language = codeMatch[1];
|
|
46259
46461
|
}
|
|
46462
|
+
if (!language && codeText) {
|
|
46463
|
+
language = inferLanguage(codeText);
|
|
46464
|
+
}
|
|
46260
46465
|
const cleanLines = codeText.split("\n").filter((line) => {
|
|
46261
46466
|
const trimmed = line.trim();
|
|
46262
46467
|
return !trimmed || !isUnwantedText(trimmed);
|
|
@@ -46264,6 +46469,30 @@ var genericPre = {
|
|
|
46264
46469
|
return "\n```" + language + "\n" + cleanLines.join("\n") + "\n```\n";
|
|
46265
46470
|
}
|
|
46266
46471
|
};
|
|
46472
|
+
var standardCodeBlock = {
|
|
46473
|
+
filter(node) {
|
|
46474
|
+
return node.nodeName === "PRE" && !!node.firstChild && node.firstChild.nodeName === "CODE";
|
|
46475
|
+
},
|
|
46476
|
+
replacement(content, node) {
|
|
46477
|
+
const codeNode = node.firstChild;
|
|
46478
|
+
const lang = codeNode.className || codeNode.getAttribute("class") || "";
|
|
46479
|
+
const langMatch = lang.match(/language-(\w+)|hljs language-(\w+)/);
|
|
46480
|
+
const language = langMatch ? langMatch[1] || langMatch[2] : "";
|
|
46481
|
+
const codeContent = codeNode.textContent || content;
|
|
46482
|
+
const cleanContent = codeContent.split("\n").filter((line) => {
|
|
46483
|
+
const trimmed = line.trim();
|
|
46484
|
+
return !isUnwantedText(trimmed);
|
|
46485
|
+
}).join("\n");
|
|
46486
|
+
return "\n```" + language + "\n" + cleanContent + "\n```\n";
|
|
46487
|
+
}
|
|
46488
|
+
};
|
|
46489
|
+
function cleanCellText(text) {
|
|
46490
|
+
let cleaned = text.replace(/\s+/g, " ");
|
|
46491
|
+
for (const t of UI_TEXT) {
|
|
46492
|
+
cleaned = cleaned.replaceAll(t, "");
|
|
46493
|
+
}
|
|
46494
|
+
return cleaned.trim();
|
|
46495
|
+
}
|
|
46267
46496
|
var harmonyTable = {
|
|
46268
46497
|
filter(node) {
|
|
46269
46498
|
return node.nodeName === "TABLE";
|
|
@@ -46282,7 +46511,7 @@ var harmonyTable = {
|
|
|
46282
46511
|
}
|
|
46283
46512
|
const code = cell.querySelector("code");
|
|
46284
46513
|
if (code) return `\`${code.textContent?.trim() ?? ""}\``;
|
|
46285
|
-
return (cell.textContent ?? "")
|
|
46514
|
+
return cleanCellText(cell.textContent ?? "");
|
|
46286
46515
|
});
|
|
46287
46516
|
if (cells.length === 0) return;
|
|
46288
46517
|
md += "| " + cells.join(" | ") + " |\n";
|
|
@@ -46294,6 +46523,49 @@ var harmonyTable = {
|
|
|
46294
46523
|
return md + "\n";
|
|
46295
46524
|
}
|
|
46296
46525
|
};
|
|
46526
|
+
var divTable = {
|
|
46527
|
+
filter(node) {
|
|
46528
|
+
if (node.nodeType !== 1 || node.nodeName !== "DIV") return false;
|
|
46529
|
+
const cls = (node.className ?? "").toLowerCase();
|
|
46530
|
+
const tableClasses = ["table", "tbl", "data-table", "table-container", "table-wrap"];
|
|
46531
|
+
if (!tableClasses.some((c) => cls.includes(c))) return false;
|
|
46532
|
+
const rowDivs = Array.from(node.children).filter((child) => {
|
|
46533
|
+
const childClass = (child.className ?? "").toLowerCase();
|
|
46534
|
+
return childClass.includes("tr") || childClass.includes("row") || childClass.includes("table-row");
|
|
46535
|
+
});
|
|
46536
|
+
return rowDivs.length > 0;
|
|
46537
|
+
},
|
|
46538
|
+
replacement(content, node) {
|
|
46539
|
+
const rows = Array.from(node.children).filter((child) => {
|
|
46540
|
+
const childClass = (child.className ?? "").toLowerCase();
|
|
46541
|
+
return childClass.includes("tr") || childClass.includes("row") || childClass.includes("table-row");
|
|
46542
|
+
});
|
|
46543
|
+
if (rows.length === 0) return content;
|
|
46544
|
+
let md = "\n";
|
|
46545
|
+
rows.forEach((row, rowIndex) => {
|
|
46546
|
+
const cells = Array.from(row.children).filter((child) => {
|
|
46547
|
+
const childClass = (child.className ?? "").toLowerCase();
|
|
46548
|
+
return childClass.includes("td") || childClass.includes("th") || childClass.includes("cell");
|
|
46549
|
+
}).map((cell) => {
|
|
46550
|
+
const link = cell.querySelector("a");
|
|
46551
|
+
if (link?.getAttribute("href")) {
|
|
46552
|
+
const href = link.getAttribute("href");
|
|
46553
|
+
const full = href.startsWith("http") ? href : `https://developer.huawei.com${href}`;
|
|
46554
|
+
return `[${link.textContent?.trim() ?? ""}](${full})`;
|
|
46555
|
+
}
|
|
46556
|
+
const code = cell.querySelector("code");
|
|
46557
|
+
if (code) return `\`${code.textContent?.trim() ?? ""}\``;
|
|
46558
|
+
return cleanCellText(cell.textContent ?? "");
|
|
46559
|
+
});
|
|
46560
|
+
if (cells.length === 0) return;
|
|
46561
|
+
md += "| " + cells.join(" | ") + " |\n";
|
|
46562
|
+
if (rowIndex === 0) {
|
|
46563
|
+
md += "| " + cells.map(() => "---").join(" | ") + " |\n";
|
|
46564
|
+
}
|
|
46565
|
+
});
|
|
46566
|
+
return md + "\n";
|
|
46567
|
+
}
|
|
46568
|
+
};
|
|
46297
46569
|
var processLinks = {
|
|
46298
46570
|
filter(node) {
|
|
46299
46571
|
return node.nodeName === "A" && !!node.getAttribute("href");
|
|
@@ -46310,10 +46582,13 @@ function addCustomRules(td) {
|
|
|
46310
46582
|
["removeCodeUIContainers", removeCodeUIContainers],
|
|
46311
46583
|
["removeUIParagraphs", removeUIParagraphs],
|
|
46312
46584
|
["removeNavigation", removeNavigation],
|
|
46585
|
+
["removeCodeUI", removeCodeUI],
|
|
46313
46586
|
["removePlatformBadge", removePlatformBadge],
|
|
46314
46587
|
["huaweiCodeBlock", huaweiCodeBlock],
|
|
46315
46588
|
["genericPre", genericPre],
|
|
46589
|
+
["standardCodeBlock", standardCodeBlock],
|
|
46316
46590
|
["harmonyTable", harmonyTable],
|
|
46591
|
+
["divTable", divTable],
|
|
46317
46592
|
["processLinks", processLinks]
|
|
46318
46593
|
];
|
|
46319
46594
|
for (const [name, rule] of rules2) {
|
|
@@ -46388,7 +46663,7 @@ function cleanupMarkdown(md) {
|
|
|
46388
46663
|
return out.trim();
|
|
46389
46664
|
}
|
|
46390
46665
|
|
|
46391
|
-
// src/tools/harmony-doc/doc-
|
|
46666
|
+
// src/tools/harmony-doc/harmony-doc-tool.ts
|
|
46392
46667
|
var docCache = /* @__PURE__ */ new Map();
|
|
46393
46668
|
var CACHE_TTL = 10 * 60 * 1e3;
|
|
46394
46669
|
function getCached(objectId) {
|
|
@@ -46400,71 +46675,110 @@ function getCached(objectId) {
|
|
|
46400
46675
|
}
|
|
46401
46676
|
return entry;
|
|
46402
46677
|
}
|
|
46403
|
-
function setCache(objectId, doc, markdown) {
|
|
46404
|
-
docCache.set(objectId, { doc, markdown, fetchedAt: Date.now() });
|
|
46678
|
+
function setCache(objectId, doc, markdown, breadcrumb) {
|
|
46679
|
+
docCache.set(objectId, { doc, markdown, breadcrumb, fetchedAt: Date.now() });
|
|
46405
46680
|
}
|
|
46406
|
-
function
|
|
46681
|
+
function harmonyDocViewTool(_managers) {
|
|
46407
46682
|
return tool({
|
|
46408
|
-
description: "
|
|
46683
|
+
description: "View a HarmonyOS official documentation page by URL with full Markdown content and pagination. Use this tool to read official API references, guides, and best practices from developer.huawei.com. Typically used with URLs obtained from skillSearch's Official Documentation results. Supports pagination for long documents \u2014 use the 'page' parameter to continue reading.",
|
|
46409
46684
|
args: {
|
|
46410
|
-
url: tool.schema.string("Full URL of the
|
|
46411
|
-
page: tool.schema.number("Page number for long documents. Starts at 1.
|
|
46685
|
+
url: tool.schema.string("Full URL of the document to view. Must be a developer.huawei.com URL, typically from skillSearch results."),
|
|
46686
|
+
page: tool.schema.number("Page number for long documents. Starts at 1.").min(1).default(1)
|
|
46412
46687
|
},
|
|
46413
46688
|
execute: async (args, _context) => {
|
|
46414
|
-
|
|
46415
|
-
|
|
46416
|
-
|
|
46417
|
-
|
|
46418
|
-
|
|
46419
|
-
|
|
46420
|
-
|
|
46421
|
-
|
|
46422
|
-
|
|
46423
|
-
|
|
46424
|
-
|
|
46425
|
-
|
|
46426
|
-
|
|
46427
|
-
|
|
46428
|
-
|
|
46429
|
-
|
|
46430
|
-
|
|
46431
|
-
|
|
46432
|
-
|
|
46433
|
-
|
|
46434
|
-
|
|
46435
|
-
|
|
46436
|
-
|
|
46437
|
-
|
|
46438
|
-
|
|
46439
|
-
|
|
46440
|
-
|
|
46441
|
-
|
|
46442
|
-
|
|
46443
|
-
|
|
46444
|
-
|
|
46689
|
+
return executeView(args.url, args.page);
|
|
46690
|
+
}
|
|
46691
|
+
});
|
|
46692
|
+
}
|
|
46693
|
+
async function executeView(url3, page) {
|
|
46694
|
+
try {
|
|
46695
|
+
log("[harmony-doc-view]", { url: url3, page });
|
|
46696
|
+
const objectId = objectIdFromUrl(url3);
|
|
46697
|
+
if (!objectId) {
|
|
46698
|
+
return `Error: Cannot extract document ID from URL: ${url3}`;
|
|
46699
|
+
}
|
|
46700
|
+
let cached3 = getCached(objectId);
|
|
46701
|
+
let doc;
|
|
46702
|
+
let markdown;
|
|
46703
|
+
let breadcrumb;
|
|
46704
|
+
if (cached3) {
|
|
46705
|
+
doc = cached3.doc;
|
|
46706
|
+
markdown = cached3.markdown;
|
|
46707
|
+
breadcrumb = cached3.breadcrumb;
|
|
46708
|
+
log("[harmony-doc-view] cache hit", { objectId, page });
|
|
46709
|
+
} else {
|
|
46710
|
+
doc = await fetchDocument(objectId);
|
|
46711
|
+
if (!doc.html) {
|
|
46712
|
+
log("[harmony-doc-view] API returned empty HTML", { objectId, title: doc.title });
|
|
46713
|
+
return `Error: Document content is empty for ${url3} (objectId: ${objectId}). The API returned no HTML content \u2014 the document may not be available through this API.`;
|
|
46714
|
+
}
|
|
46715
|
+
markdown = htmlToMarkdown(doc.html);
|
|
46716
|
+
log("[harmony-doc-view] conversion result", {
|
|
46717
|
+
objectId,
|
|
46718
|
+
htmlLength: doc.html.length,
|
|
46719
|
+
markdownLength: markdown.length,
|
|
46720
|
+
markdownPreview: markdown.substring(0, 300)
|
|
46721
|
+
});
|
|
46722
|
+
if (!markdown.trim()) {
|
|
46723
|
+
log("[harmony-doc-view] markdown conversion produced empty output", {
|
|
46724
|
+
objectId,
|
|
46725
|
+
htmlLength: doc.html.length
|
|
46726
|
+
});
|
|
46727
|
+
return `Error: Document HTML was non-empty (${doc.html.length} chars) but converted to empty markdown for ${url3}. This may indicate the HTML structure is not supported.`;
|
|
46728
|
+
}
|
|
46729
|
+
breadcrumb = [];
|
|
46730
|
+
if (doc.catalogName) {
|
|
46731
|
+
try {
|
|
46732
|
+
breadcrumb = await resolveBreadcrumb("cn", doc.catalogName, objectId);
|
|
46733
|
+
} catch (e) {
|
|
46734
|
+
log("[harmony-doc-view] breadcrumb resolution failed", { objectId, error: String(e) });
|
|
46735
|
+
}
|
|
46736
|
+
}
|
|
46737
|
+
setCache(objectId, doc, markdown, breadcrumb);
|
|
46738
|
+
log("[harmony-doc-view] cache miss, fetched from network", { objectId });
|
|
46739
|
+
}
|
|
46740
|
+
const MAX_LENGTH = 3e4;
|
|
46741
|
+
const totalPages = Math.max(1, Math.ceil(markdown.length / MAX_LENGTH));
|
|
46742
|
+
const currentPage = Math.min(page ?? 1, totalPages);
|
|
46743
|
+
const start = (currentPage - 1) * MAX_LENGTH;
|
|
46744
|
+
const end = Math.min(currentPage * MAX_LENGTH, markdown.length);
|
|
46745
|
+
const chunk = markdown.substring(start, end);
|
|
46746
|
+
let output = `# ${doc.title}
|
|
46747
|
+
|
|
46748
|
+
Source: ${url3}
|
|
46445
46749
|
`;
|
|
46446
|
-
|
|
46447
|
-
|
|
46750
|
+
if (breadcrumb.length > 0) {
|
|
46751
|
+
output += `Path: ${breadcrumb.join(" > ")}
|
|
46448
46752
|
`;
|
|
46449
|
-
|
|
46450
|
-
|
|
46451
|
-
|
|
46753
|
+
}
|
|
46754
|
+
const updateTime = formatUpdateTime(doc.displayUpdateTime);
|
|
46755
|
+
if (updateTime) {
|
|
46756
|
+
output += `Updated: ${updateTime}
|
|
46452
46757
|
`;
|
|
46453
|
-
|
|
46454
|
-
|
|
46455
|
-
|
|
46456
|
-
|
|
46457
|
-
|
|
46758
|
+
}
|
|
46759
|
+
if (totalPages > 1) {
|
|
46760
|
+
output += `Page ${currentPage} of ${totalPages}
|
|
46761
|
+
`;
|
|
46762
|
+
}
|
|
46763
|
+
output += "\n---\n\n";
|
|
46764
|
+
output += chunk;
|
|
46765
|
+
if (currentPage < totalPages) {
|
|
46766
|
+
output += `
|
|
46458
46767
|
|
|
46459
46768
|
---
|
|
46460
|
-
[Document continues \u2014 call
|
|
46461
|
-
|
|
46462
|
-
|
|
46463
|
-
|
|
46464
|
-
|
|
46465
|
-
|
|
46466
|
-
|
|
46467
|
-
|
|
46769
|
+
[Document continues \u2014 call with page=${currentPage + 1} to read more]`;
|
|
46770
|
+
}
|
|
46771
|
+
log("[harmony-doc-view] RETURN", {
|
|
46772
|
+
objectId,
|
|
46773
|
+
outputLength: output.length,
|
|
46774
|
+
chunkLength: chunk.length,
|
|
46775
|
+
markdownLength: markdown.length,
|
|
46776
|
+
outputEnd: output.substring(Math.max(0, output.length - 200))
|
|
46777
|
+
});
|
|
46778
|
+
return output;
|
|
46779
|
+
} catch (e) {
|
|
46780
|
+
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
46781
|
+
}
|
|
46468
46782
|
}
|
|
46469
46783
|
|
|
46470
46784
|
// src/tools/builtin.ts
|
|
@@ -46472,8 +46786,7 @@ function createBuiltinTools(managers) {
|
|
|
46472
46786
|
return {
|
|
46473
46787
|
createHmTemplate: createHmTemplateTool(managers),
|
|
46474
46788
|
skillSearch: skillSearchTool(managers),
|
|
46475
|
-
"harmony-doc-
|
|
46476
|
-
"harmony-doc-view": docViewTool(managers)
|
|
46789
|
+
"harmony-doc-view": harmonyDocViewTool(managers)
|
|
46477
46790
|
};
|
|
46478
46791
|
}
|
|
46479
46792
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@harmonyos-arkts/opencode-plugin",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.12",
|
|
5
5
|
"description": "HarmonyOS Full-Lifecycle Development Assistant. Specialized in the complete development lifecycle of HarmonyOS applications, including project creation, UI development, state management, network requests, data storage, permission requests, performance optimization, testing, and release.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|