@harmonyos-arkts/opencode-plugin 0.0.10 → 0.0.11

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +445 -114
  3. 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-search` 搜索华为开发者文档(含官方文档与社区内容),通过 `harmony-doc-view` 分页查看文档全文
11
+ - **HarmonyOS文档查询**:通过 `harmony-doc` 工具搜索和查看华为开发者文档(含官方文档与社区内容),支持关键词搜索和分页查看全文
12
12
 
13
13
  ## 快速开始
14
14
 
package/dist/index.js CHANGED
@@ -45368,23 +45368,63 @@ function skillSearchTool(managers) {
45368
45368
  var BASE_URL = "https://svc-drcn.developer.huawei.com/community/servlet";
45369
45369
  var HEADERS = {
45370
45370
  "Content-Type": "application/json",
45371
- "Referer": "https://developer.huawei.com/consumer/cn/doc/",
45371
+ Referer: "https://developer.huawei.com/consumer/cn/doc/",
45372
45372
  "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
45373
45373
  };
45374
+ var MAX_RETRIES = 2;
45375
+ var RETRY_DELAY_MS = 1e3;
45376
+ async function sleep(ms) {
45377
+ return new Promise((resolve) => setTimeout(resolve, ms));
45378
+ }
45374
45379
  async function post(path7, body) {
45375
- const res = await fetch(BASE_URL + path7, {
45376
- method: "POST",
45377
- headers: HEADERS,
45378
- body: JSON.stringify(body)
45379
- });
45380
- if (!res.ok) {
45381
- throw new Error(`API request failed: ${res.status} ${res.statusText} for ${path7}`);
45382
- }
45383
- const data = await res.json();
45384
- if (data.code && data.code !== 0 && data.code !== "0") {
45385
- throw new Error(`API error [${data.code}]: ${data.message} for ${path7}`);
45380
+ let lastError;
45381
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
45382
+ try {
45383
+ const res = await fetch(BASE_URL + path7, {
45384
+ method: "POST",
45385
+ headers: HEADERS,
45386
+ body: JSON.stringify(body)
45387
+ });
45388
+ if (!res.ok) {
45389
+ const retriable = res.status === 429 || res.status >= 500;
45390
+ if (retriable && attempt < MAX_RETRIES) {
45391
+ lastError = new Error(`API request failed: ${res.status} ${res.statusText} for ${path7}`);
45392
+ await sleep(RETRY_DELAY_MS * (attempt + 1));
45393
+ continue;
45394
+ }
45395
+ throw new Error(`API request failed: ${res.status} ${res.statusText} for ${path7}`);
45396
+ }
45397
+ const data = await res.json();
45398
+ if (data.code && data.code !== 0 && data.code !== "0") {
45399
+ throw new Error(`API error [${data.code}]: ${data.message} for ${path7}`);
45400
+ }
45401
+ return data;
45402
+ } catch (e) {
45403
+ if (e instanceof TypeError && e.message.includes("fetch")) {
45404
+ lastError = e;
45405
+ if (attempt < MAX_RETRIES) {
45406
+ await sleep(RETRY_DELAY_MS * (attempt + 1));
45407
+ continue;
45408
+ }
45409
+ }
45410
+ throw e;
45411
+ }
45386
45412
  }
45387
- return data;
45413
+ throw lastError ?? new Error("Request failed after retries");
45414
+ }
45415
+ var CATALOG_DISPLAY_NAMES = {
45416
+ "harmonyos-guides": "\u6307\u5357",
45417
+ "harmonyos-references": "API\u53C2\u8003",
45418
+ "harmonyos-best-practices": "\u6700\u4F73\u5B9E\u8DF5",
45419
+ "harmonyos-faqs": "FAQ",
45420
+ "design-guides": "\u8BBE\u8BA1\u6307\u5357",
45421
+ "atomic-guides": "\u6307\u5357",
45422
+ "atomic-references": "API\u53C2\u8003",
45423
+ "atomic-faqs": "FAQ",
45424
+ "atomic-ascf": "ASCF\u6846\u67B6"
45425
+ };
45426
+ function catalogDisplayName(catalogName) {
45427
+ return CATALOG_DISPLAY_NAMES[catalogName] || "";
45388
45428
  }
45389
45429
  async function searchDocs(keyword, maxResults = 10) {
45390
45430
  const data = await post(
@@ -45405,12 +45445,29 @@ async function searchDocs(keyword, maxResults = 10) {
45405
45445
  }
45406
45446
  );
45407
45447
  return (data.resultList ?? []).map((r) => {
45448
+ let subsection = "";
45449
+ let anchorId = "";
45450
+ if (r.anchorHighlightInfo) {
45451
+ subsection = r.anchorHighlightInfo.replace(/<\/?em>/g, "");
45452
+ }
45408
45453
  let breadcrumb = [];
45409
45454
  try {
45410
45455
  const ext = typeof r.metaData?.ext === "string" ? JSON.parse(r.metaData.ext) : r.metaData?.ext ?? {};
45411
45456
  const nodeNames = Array.isArray(ext.nodeNames) ? ext.nodeNames : [];
45457
+ const catalogName = typeof ext.catalogName === "string" ? ext.catalogName : "";
45412
45458
  if (nodeNames.length > 0) {
45413
- breadcrumb = nodeNames;
45459
+ const rootName = catalogDisplayName(catalogName) || catalogName;
45460
+ breadcrumb = rootName ? [rootName, ...nodeNames] : nodeNames;
45461
+ }
45462
+ if (subsection) {
45463
+ try {
45464
+ const anchors = typeof ext.anchorList === "string" ? JSON.parse(ext.anchorList) : ext.anchorList;
45465
+ if (Array.isArray(anchors)) {
45466
+ const match = anchors.find((a) => a.title === subsection);
45467
+ if (match) anchorId = match.anchorId ?? "";
45468
+ }
45469
+ } catch {
45470
+ }
45414
45471
  }
45415
45472
  } catch {
45416
45473
  }
@@ -45420,15 +45477,31 @@ async function searchDocs(keyword, maxResults = 10) {
45420
45477
  excerpt: (r.content ?? "").replace(/\n/g, " ").substring(0, 200),
45421
45478
  breadcrumb,
45422
45479
  contentType: r.metaData?.type ?? 0,
45423
- timestamp: r.metaData?.timestamp ?? ""
45480
+ timestamp: r.metaData?.timestamp ?? "",
45481
+ subsection,
45482
+ anchorId
45424
45483
  };
45425
45484
  });
45426
45485
  }
45427
45486
  function objectIdFromUrl(url3) {
45428
45487
  try {
45429
- const pathname = new URL(url3).pathname.replace(/\/$/, "");
45488
+ const parsed = new URL(url3);
45489
+ const pathname = parsed.pathname.replace(/\/$/, "");
45490
+ const queryId = parsed.searchParams.get("objectId") || parsed.searchParams.get("id");
45491
+ if (queryId) return queryId;
45430
45492
  const parts = pathname.split("/");
45431
- return parts[parts.length - 1] || null;
45493
+ const lastSegment = parts[parts.length - 1];
45494
+ if (lastSegment && lastSegment.length > 2) return lastSegment;
45495
+ if (parts.length >= 2) {
45496
+ const secondLast = parts[parts.length - 2];
45497
+ if (secondLast && secondLast.length > 2) return secondLast;
45498
+ }
45499
+ const hash3 = parsed.hash;
45500
+ if (hash3) {
45501
+ const hashId = hash3.replace(/^#/, "");
45502
+ if (hashId.length > 5) return hashId;
45503
+ }
45504
+ return null;
45432
45505
  } catch {
45433
45506
  return null;
45434
45507
  }
@@ -45442,50 +45515,72 @@ async function fetchDocument(objectId, language = "cn") {
45442
45515
  if (!v) {
45443
45516
  throw new Error(`Empty response for objectId: ${objectId}`);
45444
45517
  }
45518
+ const html = v.content?.content ?? "";
45445
45519
  return {
45446
45520
  title: v.title ?? "",
45447
- html: v.content?.content ?? "",
45521
+ html,
45448
45522
  objectId,
45449
45523
  catalogName: v.catalogName ?? "",
45450
45524
  displayUpdateTime: v.displayUpdateTime ?? ""
45451
45525
  };
45452
45526
  }
45453
-
45454
- // src/tools/harmony-doc/doc-search-tool.ts
45455
- function docSearchTool(_managers) {
45456
- return tool({
45457
- description: "Search HarmonyOS developer documentation by keyword. Results cover official docs, API references, guides, and community content from developer.huawei.com. The breadcrumb path in each result helps identify the source type \u2014 prefer official docs over community posts when accuracy is critical. HarmonyOS APIs evolve rapidly; prefer searching for the latest information. After finding relevant results, use the harmony-doc-view tool with the URL to read the full document.",
45458
- args: {
45459
- query: tool.schema.string("Search keyword or phrase. Use specific terms for best results, e.g. 'Button component', 'router navigation', 'NetworkRequest'."),
45460
- maxResults: tool.schema.number("Maximum number of search results to return.").min(1).max(20).default(10)
45461
- },
45462
- execute: async (args, _context) => {
45463
- try {
45464
- log("[harmony-doc-search]", { query: args.query, maxResults: args.maxResults });
45465
- const results = await searchDocs(args.query, args.maxResults);
45466
- if (results.length === 0) {
45467
- return `No results found for "${args.query}". Try different keywords.`;
45468
- }
45469
- const formatted = results.map((r, i) => {
45470
- const parts = [];
45471
- parts.push(`${i + 1}. **${r.title}**`);
45472
- if (r.breadcrumb.length > 0) {
45473
- parts.push(` Path: ${r.breadcrumb.join(" > ")}`);
45474
- }
45475
- parts.push(` URL: ${r.url}`);
45476
- if (r.excerpt) {
45477
- parts.push(` ${r.excerpt}`);
45478
- }
45479
- return parts.join("\n");
45480
- }).join("\n\n");
45481
- return `Found ${results.length} results for "${args.query}":
45482
-
45483
- ${formatted}`;
45484
- } catch (e) {
45485
- return `Error: ${e instanceof Error ? e.message : String(e)}`;
45527
+ var treeCache = /* @__PURE__ */ new Map();
45528
+ async function getCatalogTree(language, catalogName, objectId) {
45529
+ const body = { language, catalogName };
45530
+ if (objectId) body.objectId = objectId;
45531
+ const data = await post(
45532
+ "/consumer/cn/documentPortal/getCatalogTree",
45533
+ body
45534
+ );
45535
+ return data.value?.catalogTreeList ?? [];
45536
+ }
45537
+ async function resolveBreadcrumb(language, catalogName, relateDocument) {
45538
+ if (!relateDocument) return [];
45539
+ const cacheKey = `${language}:${catalogName}`;
45540
+ if (!treeCache.has(cacheKey)) {
45541
+ const nodes = await getCatalogTree(language, catalogName);
45542
+ const map4 = /* @__PURE__ */ new Map();
45543
+ const walk = (list) => {
45544
+ for (const node of list) {
45545
+ map4.set(node.nodeId, { name: node.nodeName, parent: node.parent, rel: node.relateDocument });
45546
+ if (node.children?.length) walk(node.children);
45486
45547
  }
45548
+ };
45549
+ walk(nodes);
45550
+ treeCache.set(cacheKey, map4);
45551
+ }
45552
+ const map3 = treeCache.get(cacheKey);
45553
+ let target;
45554
+ for (const node of map3.values()) {
45555
+ if (node.rel === relateDocument) {
45556
+ target = node;
45557
+ break;
45487
45558
  }
45488
- });
45559
+ }
45560
+ if (!target) return [];
45561
+ const breadcrumb = [];
45562
+ let current = target;
45563
+ while (current) {
45564
+ breadcrumb.unshift(current.name);
45565
+ current = current.parent ? map3.get(current.parent) : void 0;
45566
+ }
45567
+ const rootName = catalogDisplayName(catalogName);
45568
+ if (rootName && breadcrumb[0] !== rootName) {
45569
+ breadcrumb.unshift(rootName);
45570
+ }
45571
+ return breadcrumb;
45572
+ }
45573
+ function formatUpdateTime(utcTimeStr) {
45574
+ if (!utcTimeStr) return "";
45575
+ const dt = /* @__PURE__ */ new Date(utcTimeStr.replace(" ", "T") + "Z");
45576
+ if (isNaN(dt.getTime())) return utcTimeStr;
45577
+ const bj = new Date(dt.getTime() + 8 * 60 * 60 * 1e3);
45578
+ const y = bj.getUTCFullYear();
45579
+ const m = String(bj.getUTCMonth() + 1).padStart(2, "0");
45580
+ const d = String(bj.getUTCDate()).padStart(2, "0");
45581
+ const h = String(bj.getUTCHours()).padStart(2, "0");
45582
+ const min = String(bj.getUTCMinutes()).padStart(2, "0");
45583
+ return `${y}-${m}-${d} ${h}:${min}`;
45489
45584
  }
45490
45585
 
45491
45586
  // node_modules/turndown/lib/turndown.es.js
@@ -46176,18 +46271,61 @@ var removeUIParagraphs = {
46176
46271
  return "";
46177
46272
  }
46178
46273
  };
46274
+ var NAV_SHORT_TEXT = /* @__PURE__ */ new Set([
46275
+ "\u7B80\u4F53\u4E2D\u6587",
46276
+ "English",
46277
+ "\u4E0B\u8F7D App",
46278
+ "\u63A2\u7D22",
46279
+ "\u8BBE\u8BA1",
46280
+ "\u5F00\u53D1",
46281
+ "\u5206\u53D1",
46282
+ "\u63A8\u5E7F\u4E0E\u53D8\u73B0",
46283
+ "\u751F\u6001\u5408\u4F5C",
46284
+ "\u652F\u6301",
46285
+ "\u66F4\u591A",
46286
+ "\u7ACB\u5373\u767B\u5F55",
46287
+ "\u8F93\u5165\u5173\u952E\u5B57\u641C\u7D22",
46288
+ "Hello\uFF0C",
46289
+ "\u6B22\u8FCE\u6765\u5230\u5F00\u53D1\u8005\u8054\u76DF",
46290
+ "CTRL+K",
46291
+ "Created with Pixso",
46292
+ "\u7248\u672C\u8BF4\u660E",
46293
+ "\u6307\u5357",
46294
+ "API\u53C2\u8003",
46295
+ "\u6700\u4F73\u5B9E\u8DF5",
46296
+ "FAQ",
46297
+ "\u53D8\u66F4\u9884\u544A",
46298
+ "\u591A\u8BBE\u5907\u573A\u666F",
46299
+ "\u624B\u673A",
46300
+ "\u5E94\u7528\u8D28\u91CF",
46301
+ "\u6280\u672F\u8D28\u91CF",
46302
+ "\u5F00\u53D1\u8005\u80FD\u529B\u8BA4\u8BC1",
46303
+ "\u6211\u7684",
46304
+ "\u7BA1\u7406\u4E2D\u5FC3",
46305
+ "\u4E2A\u4EBA\u4E2D\u5FC3",
46306
+ "\u6211\u7684\u5B66\u5802",
46307
+ "\u6211\u7684\u6536\u85CF",
46308
+ "\u6211\u7684\u6D3B\u52A8",
46309
+ "\u6211\u7684\u5DE5\u5355"
46310
+ ]);
46179
46311
  var removeNavigation = {
46180
46312
  filter(node) {
46181
46313
  if (node.nodeType !== 1) return false;
46314
+ const text = (node.textContent ?? "").trim();
46315
+ if (text.length <= 20 && NAV_SHORT_TEXT.has(text)) {
46316
+ return true;
46317
+ }
46318
+ if (node.nodeName === "P" && text.length === 0) {
46319
+ const hasContent = node.querySelector("img, code, pre, table");
46320
+ if (!hasContent) return true;
46321
+ }
46182
46322
  const cls = node.className?.toLowerCase() ?? "";
46183
46323
  const id = node.id?.toLowerCase() ?? "";
46184
46324
  const unwantedClasses = [
46185
46325
  "top-bar",
46186
46326
  "menu-bar",
46187
- "sidebar",
46188
46327
  "search-bar",
46189
46328
  "breadcrumb",
46190
- "toolbar",
46191
46329
  "footer-nav",
46192
46330
  "main-nav",
46193
46331
  "side-nav",
@@ -46204,7 +46342,29 @@ var removeNavigation = {
46204
46342
  "feedback-section",
46205
46343
  "qrcode-section"
46206
46344
  ];
46207
- return unwantedClasses.some((c) => cls.includes(c) || id.includes(c));
46345
+ const classList = cls.split(/\s+/);
46346
+ const matchesClass = classList.some(
46347
+ (c) => unwantedClasses.some((u) => c === u || c.startsWith(u + "-") || c.endsWith("-" + u))
46348
+ );
46349
+ const matchesId = unwantedClasses.some(
46350
+ (u) => id === u || id.startsWith(u + "-") || id.endsWith("-" + u)
46351
+ );
46352
+ return matchesClass || matchesId;
46353
+ },
46354
+ replacement() {
46355
+ return "";
46356
+ }
46357
+ };
46358
+ var removeCodeUI = {
46359
+ filter(node) {
46360
+ if (node.nodeType === 3) {
46361
+ return isUnwantedText(node.textContent ?? "");
46362
+ }
46363
+ if (node.nodeType === 1 && ["P", "DIV", "SPAN"].includes(node.nodeName)) {
46364
+ const text = (node.textContent ?? "").trim();
46365
+ return isUnwantedText(text);
46366
+ }
46367
+ return false;
46208
46368
  },
46209
46369
  replacement() {
46210
46370
  return "";
@@ -46240,6 +46400,18 @@ var huaweiCodeBlock = {
46240
46400
  return "\n```" + language + "\n" + lines.join("\n") + "\n```\n";
46241
46401
  }
46242
46402
  };
46403
+ function inferLanguage(codeText) {
46404
+ if (codeText.includes("import ") || codeText.includes("export ") || codeText.includes("interface ")) {
46405
+ return "typescript";
46406
+ }
46407
+ if (codeText.includes("#include") || codeText.includes("int main")) {
46408
+ return "cpp";
46409
+ }
46410
+ if (codeText.includes("public class")) {
46411
+ return "java";
46412
+ }
46413
+ return "";
46414
+ }
46243
46415
  var genericPre = {
46244
46416
  filter(node) {
46245
46417
  return node.nodeName === "PRE" && !node.querySelector("ol.linenums");
@@ -46257,6 +46429,9 @@ var genericPre = {
46257
46429
  const codeMatch = (codeElem.className ?? "").match(/language-(\w+)/);
46258
46430
  if (codeMatch) language = codeMatch[1];
46259
46431
  }
46432
+ if (!language && codeText) {
46433
+ language = inferLanguage(codeText);
46434
+ }
46260
46435
  const cleanLines = codeText.split("\n").filter((line) => {
46261
46436
  const trimmed = line.trim();
46262
46437
  return !trimmed || !isUnwantedText(trimmed);
@@ -46264,6 +46439,30 @@ var genericPre = {
46264
46439
  return "\n```" + language + "\n" + cleanLines.join("\n") + "\n```\n";
46265
46440
  }
46266
46441
  };
46442
+ var standardCodeBlock = {
46443
+ filter(node) {
46444
+ return node.nodeName === "PRE" && !!node.firstChild && node.firstChild.nodeName === "CODE";
46445
+ },
46446
+ replacement(content, node) {
46447
+ const codeNode = node.firstChild;
46448
+ const lang = codeNode.className || codeNode.getAttribute("class") || "";
46449
+ const langMatch = lang.match(/language-(\w+)|hljs language-(\w+)/);
46450
+ const language = langMatch ? langMatch[1] || langMatch[2] : "";
46451
+ const codeContent = codeNode.textContent || content;
46452
+ const cleanContent = codeContent.split("\n").filter((line) => {
46453
+ const trimmed = line.trim();
46454
+ return !isUnwantedText(trimmed);
46455
+ }).join("\n");
46456
+ return "\n```" + language + "\n" + cleanContent + "\n```\n";
46457
+ }
46458
+ };
46459
+ function cleanCellText(text) {
46460
+ let cleaned = text.replace(/\s+/g, " ");
46461
+ for (const t of UI_TEXT) {
46462
+ cleaned = cleaned.replaceAll(t, "");
46463
+ }
46464
+ return cleaned.trim();
46465
+ }
46267
46466
  var harmonyTable = {
46268
46467
  filter(node) {
46269
46468
  return node.nodeName === "TABLE";
@@ -46282,7 +46481,7 @@ var harmonyTable = {
46282
46481
  }
46283
46482
  const code = cell.querySelector("code");
46284
46483
  if (code) return `\`${code.textContent?.trim() ?? ""}\``;
46285
- return (cell.textContent ?? "").replace(/\s+/g, " ").trim();
46484
+ return cleanCellText(cell.textContent ?? "");
46286
46485
  });
46287
46486
  if (cells.length === 0) return;
46288
46487
  md += "| " + cells.join(" | ") + " |\n";
@@ -46294,6 +46493,49 @@ var harmonyTable = {
46294
46493
  return md + "\n";
46295
46494
  }
46296
46495
  };
46496
+ var divTable = {
46497
+ filter(node) {
46498
+ if (node.nodeType !== 1 || node.nodeName !== "DIV") return false;
46499
+ const cls = (node.className ?? "").toLowerCase();
46500
+ const tableClasses = ["table", "tbl", "data-table", "table-container", "table-wrap"];
46501
+ if (!tableClasses.some((c) => cls.includes(c))) return false;
46502
+ const rowDivs = Array.from(node.children).filter((child) => {
46503
+ const childClass = (child.className ?? "").toLowerCase();
46504
+ return childClass.includes("tr") || childClass.includes("row") || childClass.includes("table-row");
46505
+ });
46506
+ return rowDivs.length > 0;
46507
+ },
46508
+ replacement(content, node) {
46509
+ const rows = Array.from(node.children).filter((child) => {
46510
+ const childClass = (child.className ?? "").toLowerCase();
46511
+ return childClass.includes("tr") || childClass.includes("row") || childClass.includes("table-row");
46512
+ });
46513
+ if (rows.length === 0) return content;
46514
+ let md = "\n";
46515
+ rows.forEach((row, rowIndex) => {
46516
+ const cells = Array.from(row.children).filter((child) => {
46517
+ const childClass = (child.className ?? "").toLowerCase();
46518
+ return childClass.includes("td") || childClass.includes("th") || childClass.includes("cell");
46519
+ }).map((cell) => {
46520
+ const link = cell.querySelector("a");
46521
+ if (link?.getAttribute("href")) {
46522
+ const href = link.getAttribute("href");
46523
+ const full = href.startsWith("http") ? href : `https://developer.huawei.com${href}`;
46524
+ return `[${link.textContent?.trim() ?? ""}](${full})`;
46525
+ }
46526
+ const code = cell.querySelector("code");
46527
+ if (code) return `\`${code.textContent?.trim() ?? ""}\``;
46528
+ return cleanCellText(cell.textContent ?? "");
46529
+ });
46530
+ if (cells.length === 0) return;
46531
+ md += "| " + cells.join(" | ") + " |\n";
46532
+ if (rowIndex === 0) {
46533
+ md += "| " + cells.map(() => "---").join(" | ") + " |\n";
46534
+ }
46535
+ });
46536
+ return md + "\n";
46537
+ }
46538
+ };
46297
46539
  var processLinks = {
46298
46540
  filter(node) {
46299
46541
  return node.nodeName === "A" && !!node.getAttribute("href");
@@ -46310,10 +46552,13 @@ function addCustomRules(td) {
46310
46552
  ["removeCodeUIContainers", removeCodeUIContainers],
46311
46553
  ["removeUIParagraphs", removeUIParagraphs],
46312
46554
  ["removeNavigation", removeNavigation],
46555
+ ["removeCodeUI", removeCodeUI],
46313
46556
  ["removePlatformBadge", removePlatformBadge],
46314
46557
  ["huaweiCodeBlock", huaweiCodeBlock],
46315
46558
  ["genericPre", genericPre],
46559
+ ["standardCodeBlock", standardCodeBlock],
46316
46560
  ["harmonyTable", harmonyTable],
46561
+ ["divTable", divTable],
46317
46562
  ["processLinks", processLinks]
46318
46563
  ];
46319
46564
  for (const [name, rule] of rules2) {
@@ -46388,7 +46633,7 @@ function cleanupMarkdown(md) {
46388
46633
  return out.trim();
46389
46634
  }
46390
46635
 
46391
- // src/tools/harmony-doc/doc-view-tool.ts
46636
+ // src/tools/harmony-doc/harmony-doc-tool.ts
46392
46637
  var docCache = /* @__PURE__ */ new Map();
46393
46638
  var CACHE_TTL = 10 * 60 * 1e3;
46394
46639
  function getCached(objectId) {
@@ -46400,71 +46645,158 @@ function getCached(objectId) {
46400
46645
  }
46401
46646
  return entry;
46402
46647
  }
46403
- function setCache(objectId, doc, markdown) {
46404
- docCache.set(objectId, { doc, markdown, fetchedAt: Date.now() });
46648
+ function setCache(objectId, doc, markdown, breadcrumb) {
46649
+ docCache.set(objectId, { doc, markdown, breadcrumb, fetchedAt: Date.now() });
46405
46650
  }
46406
- function docViewTool(_managers) {
46651
+ function harmonyDocTool(_managers) {
46407
46652
  return tool({
46408
- description: "Fetch and display the content of a HarmonyOS developer document by its URL. Use this tool after harmony-doc-search to read the full content of a relevant document. The URL should be from developer.huawei.com (e.g. from search results). For long documents, use the page parameter to read content in chunks.",
46653
+ description: "HarmonyOS developer documentation tool with two actions: 'search' and 'view'. Use 'search' to find documents by keyword; use 'view' to read a specific document by URL. Typical workflow: search first, then view the relevant result. NOTE: This tool makes network requests and may take longer to respond. Prefer using skillSearch first for component usage, Kit capabilities, and common development patterns \u2014 only fall back to this tool when skillSearch does not cover the topic.",
46409
46654
  args: {
46410
- url: tool.schema.string("Full URL of the HarmonyOS document to view. Must be a developer.huawei.com document URL."),
46411
- page: tool.schema.number("Page number for long documents. Starts at 1. Use this when the response indicates more pages are available.").min(1).default(1)
46655
+ action: tool.schema.enum(["search", "view"]).describe("Action to perform: 'search' to find documents by keyword, 'view' to read a document by URL."),
46656
+ query: tool.schema.string("Search keyword or phrase (for action='search'). Use specific terms, e.g. 'Button component', 'router navigation'.").optional(),
46657
+ maxResults: tool.schema.number("Maximum search results to return (for action='search').").min(1).max(20).default(10),
46658
+ url: tool.schema.string("Full URL of the document to view (for action='view'). Must be a developer.huawei.com URL from search results.").optional(),
46659
+ page: tool.schema.number("Page number for long documents (for action='view'). Starts at 1.").min(1).default(1)
46412
46660
  },
46413
46661
  execute: async (args, _context) => {
46414
- try {
46415
- log("[harmony-doc-view]", { url: args.url, page: args.page });
46416
- const objectId = objectIdFromUrl(args.url);
46417
- if (!objectId) {
46418
- return `Error: Cannot extract document ID from URL: ${args.url}`;
46419
- }
46420
- let cached3 = getCached(objectId);
46421
- let doc;
46422
- let markdown;
46423
- if (cached3) {
46424
- doc = cached3.doc;
46425
- markdown = cached3.markdown;
46426
- log("[harmony-doc-view] cache hit", { objectId, page: args.page });
46427
- } else {
46428
- doc = await fetchDocument(objectId);
46429
- if (!doc.html) {
46430
- return `Error: Document content is empty for ${args.url}`;
46431
- }
46432
- markdown = htmlToMarkdown(doc.html);
46433
- setCache(objectId, doc, markdown);
46434
- log("[harmony-doc-view] cache miss, fetched from network", { objectId });
46435
- }
46436
- const MAX_LENGTH = 3e4;
46437
- const totalPages = Math.max(1, Math.ceil(markdown.length / MAX_LENGTH));
46438
- const page = Math.min(args.page, totalPages);
46439
- const start = (page - 1) * MAX_LENGTH;
46440
- const end = Math.min(page * MAX_LENGTH, markdown.length);
46441
- const chunk = markdown.substring(start, end);
46442
- let output = `# ${doc.title}
46443
-
46444
- Source: ${args.url}
46662
+ if (args.action === "search") {
46663
+ return executeSearch(args.query, args.maxResults);
46664
+ }
46665
+ return executeView(args.url, args.page);
46666
+ }
46667
+ });
46668
+ }
46669
+ async function executeSearch(query, maxResults) {
46670
+ if (!query) {
46671
+ return "Error: 'query' is required when action is 'search'.";
46672
+ }
46673
+ try {
46674
+ log("[harmony-doc search]", { query, maxResults });
46675
+ const results = await searchDocs(query, maxResults ?? 10);
46676
+ if (results.length === 0) {
46677
+ return `No results found for "${query}". Try different keywords.`;
46678
+ }
46679
+ const formatted = results.map((r, i) => {
46680
+ const parts = [];
46681
+ let title = r.title;
46682
+ if (r.subsection && r.subsection !== r.title) {
46683
+ title += ` #${r.subsection}`;
46684
+ }
46685
+ parts.push(`${i + 1}. **${title}**`);
46686
+ if (r.breadcrumb.length > 0) {
46687
+ parts.push(` Path: ${r.breadcrumb.join(" > ")}`);
46688
+ }
46689
+ let displayUrl = r.url;
46690
+ if (r.anchorId) {
46691
+ displayUrl += `#${r.anchorId}`;
46692
+ }
46693
+ parts.push(` URL: ${displayUrl}`);
46694
+ if (r.excerpt) {
46695
+ parts.push(` ${r.excerpt}`);
46696
+ }
46697
+ return parts.join("\n");
46698
+ }).join("\n\n");
46699
+ return `Found ${results.length} results for "${query}":
46700
+
46701
+ ${formatted}
46702
+
46703
+ Use action='view' with the URL to read the full document.`;
46704
+ } catch (e) {
46705
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
46706
+ }
46707
+ }
46708
+ async function executeView(url3, page) {
46709
+ if (!url3) {
46710
+ return "Error: 'url' is required when action is 'view'.";
46711
+ }
46712
+ try {
46713
+ log("[harmony-doc view]", { url: url3, page });
46714
+ const objectId = objectIdFromUrl(url3);
46715
+ if (!objectId) {
46716
+ return `Error: Cannot extract document ID from URL: ${url3}`;
46717
+ }
46718
+ let cached3 = getCached(objectId);
46719
+ let doc;
46720
+ let markdown;
46721
+ let breadcrumb;
46722
+ if (cached3) {
46723
+ doc = cached3.doc;
46724
+ markdown = cached3.markdown;
46725
+ breadcrumb = cached3.breadcrumb;
46726
+ log("[harmony-doc view] cache hit", { objectId, page });
46727
+ } else {
46728
+ doc = await fetchDocument(objectId);
46729
+ if (!doc.html) {
46730
+ log("[harmony-doc view] API returned empty HTML", { objectId, title: doc.title });
46731
+ 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.`;
46732
+ }
46733
+ markdown = htmlToMarkdown(doc.html);
46734
+ log("[harmony-doc view] conversion result", {
46735
+ objectId,
46736
+ htmlLength: doc.html.length,
46737
+ markdownLength: markdown.length,
46738
+ markdownPreview: markdown.substring(0, 300)
46739
+ });
46740
+ if (!markdown.trim()) {
46741
+ log("[harmony-doc view] markdown conversion produced empty output", {
46742
+ objectId,
46743
+ htmlLength: doc.html.length
46744
+ });
46745
+ 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.`;
46746
+ }
46747
+ breadcrumb = [];
46748
+ if (doc.catalogName) {
46749
+ try {
46750
+ breadcrumb = await resolveBreadcrumb("cn", doc.catalogName, objectId);
46751
+ } catch (e) {
46752
+ log("[harmony-doc view] breadcrumb resolution failed", { objectId, error: String(e) });
46753
+ }
46754
+ }
46755
+ setCache(objectId, doc, markdown, breadcrumb);
46756
+ log("[harmony-doc view] cache miss, fetched from network", { objectId });
46757
+ }
46758
+ const MAX_LENGTH = 3e4;
46759
+ const totalPages = Math.max(1, Math.ceil(markdown.length / MAX_LENGTH));
46760
+ const currentPage = Math.min(page ?? 1, totalPages);
46761
+ const start = (currentPage - 1) * MAX_LENGTH;
46762
+ const end = Math.min(currentPage * MAX_LENGTH, markdown.length);
46763
+ const chunk = markdown.substring(start, end);
46764
+ let output = `# ${doc.title}
46765
+
46766
+ Source: ${url3}
46445
46767
  `;
46446
- if (doc.displayUpdateTime) {
46447
- output += `Updated: ${doc.displayUpdateTime}
46768
+ if (breadcrumb.length > 0) {
46769
+ output += `Path: ${breadcrumb.join(" > ")}
46448
46770
  `;
46449
- }
46450
- if (totalPages > 1) {
46451
- output += `Page ${page} of ${totalPages}
46771
+ }
46772
+ const updateTime = formatUpdateTime(doc.displayUpdateTime);
46773
+ if (updateTime) {
46774
+ output += `Updated: ${updateTime}
46452
46775
  `;
46453
- }
46454
- output += "\n---\n\n";
46455
- output += chunk;
46456
- if (page < totalPages) {
46457
- output += `
46776
+ }
46777
+ if (totalPages > 1) {
46778
+ output += `Page ${currentPage} of ${totalPages}
46779
+ `;
46780
+ }
46781
+ output += "\n---\n\n";
46782
+ output += chunk;
46783
+ if (currentPage < totalPages) {
46784
+ output += `
46458
46785
 
46459
46786
  ---
46460
- [Document continues \u2014 call harmony-doc-view with page=${page + 1} to read more]`;
46461
- }
46462
- return output;
46463
- } catch (e) {
46464
- return `Error: ${e instanceof Error ? e.message : String(e)}`;
46465
- }
46466
- }
46467
- });
46787
+ [Document continues \u2014 call with action='view' and page=${currentPage + 1} to read more]`;
46788
+ }
46789
+ log("[harmony-doc view] RETURN", {
46790
+ objectId,
46791
+ outputLength: output.length,
46792
+ chunkLength: chunk.length,
46793
+ markdownLength: markdown.length,
46794
+ outputEnd: output.substring(Math.max(0, output.length - 200))
46795
+ });
46796
+ return output;
46797
+ } catch (e) {
46798
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
46799
+ }
46468
46800
  }
46469
46801
 
46470
46802
  // src/tools/builtin.ts
@@ -46472,8 +46804,7 @@ function createBuiltinTools(managers) {
46472
46804
  return {
46473
46805
  createHmTemplate: createHmTemplateTool(managers),
46474
46806
  skillSearch: skillSearchTool(managers),
46475
- "harmony-doc-search": docSearchTool(managers),
46476
- "harmony-doc-view": docViewTool(managers)
46807
+ "harmony-doc": harmonyDocTool(managers)
46477
46808
  };
46478
46809
  }
46479
46810
 
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.10",
4
+ "version": "0.0.11",
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",