@harmonyos-arkts/opencode-plugin 0.0.11 → 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.
Files changed (2) hide show
  1. package/dist/index.js +112 -130
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -45333,37 +45333,6 @@ 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 = {
@@ -45371,46 +45340,28 @@ var HEADERS = {
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
  };
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
- }
45343
+ var REQUEST_TIMEOUT_MS = 1e4;
45379
45344
  async function post(path7, body) {
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;
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}`);
45411
45360
  }
45361
+ return data;
45362
+ } finally {
45363
+ clearTimeout(timer);
45412
45364
  }
45413
- throw lastError ?? new Error("Request failed after retries");
45414
45365
  }
45415
45366
  var CATALOG_DISPLAY_NAMES = {
45416
45367
  "harmonyos-guides": "\u6307\u5357",
@@ -45475,6 +45426,7 @@ async function searchDocs(keyword, maxResults = 10) {
45475
45426
  title: r.name ?? "",
45476
45427
  url: r.url ?? "",
45477
45428
  excerpt: (r.content ?? "").replace(/\n/g, " ").substring(0, 200),
45429
+ excerptTruncated: (r.content ?? "").replace(/\n/g, " ").length > 200,
45478
45430
  breadcrumb,
45479
45431
  contentType: r.metaData?.type ?? 0,
45480
45432
  timestamp: r.metaData?.timestamp ?? "",
@@ -45583,6 +45535,84 @@ function formatUpdateTime(utcTimeStr) {
45583
45535
  return `${y}-${m}-${d} ${h}:${min}`;
45584
45536
  }
45585
45537
 
45538
+ // src/tools/skill-search/skill-search-tool.ts
45539
+ function skillSearchTool(managers) {
45540
+ return tool({
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.",
45542
+ args: {
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)
45546
+ },
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}`);
45594
+ if (r.excerpt) {
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(" > ")}`);
45600
+ }
45601
+ return lines.join("\n");
45602
+ }).join("\n\n");
45603
+ parts.push(
45604
+ `## Official Documentation
45605
+
45606
+ ${docInfo}
45607
+
45608
+ Use harmony-doc-view with a URL above to read any official document in full.`
45609
+ );
45610
+ }
45611
+ return parts.join("\n\n");
45612
+ }
45613
+ });
45614
+ }
45615
+
45586
45616
  // node_modules/turndown/lib/turndown.es.js
45587
45617
  function extend3(destination) {
45588
45618
  for (var i = 1; i < arguments.length; i++) {
@@ -46648,69 +46678,21 @@ function getCached(objectId) {
46648
46678
  function setCache(objectId, doc, markdown, breadcrumb) {
46649
46679
  docCache.set(objectId, { doc, markdown, breadcrumb, fetchedAt: Date.now() });
46650
46680
  }
46651
- function harmonyDocTool(_managers) {
46681
+ function harmonyDocViewTool(_managers) {
46652
46682
  return tool({
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.",
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.",
46654
46684
  args: {
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)
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)
46660
46687
  },
46661
46688
  execute: async (args, _context) => {
46662
- if (args.action === "search") {
46663
- return executeSearch(args.query, args.maxResults);
46664
- }
46665
46689
  return executeView(args.url, args.page);
46666
46690
  }
46667
46691
  });
46668
46692
  }
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
46693
  async function executeView(url3, page) {
46709
- if (!url3) {
46710
- return "Error: 'url' is required when action is 'view'.";
46711
- }
46712
46694
  try {
46713
- log("[harmony-doc view]", { url: url3, page });
46695
+ log("[harmony-doc-view]", { url: url3, page });
46714
46696
  const objectId = objectIdFromUrl(url3);
46715
46697
  if (!objectId) {
46716
46698
  return `Error: Cannot extract document ID from URL: ${url3}`;
@@ -46723,22 +46705,22 @@ async function executeView(url3, page) {
46723
46705
  doc = cached3.doc;
46724
46706
  markdown = cached3.markdown;
46725
46707
  breadcrumb = cached3.breadcrumb;
46726
- log("[harmony-doc view] cache hit", { objectId, page });
46708
+ log("[harmony-doc-view] cache hit", { objectId, page });
46727
46709
  } else {
46728
46710
  doc = await fetchDocument(objectId);
46729
46711
  if (!doc.html) {
46730
- log("[harmony-doc view] API returned empty HTML", { objectId, title: doc.title });
46712
+ log("[harmony-doc-view] API returned empty HTML", { objectId, title: doc.title });
46731
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.`;
46732
46714
  }
46733
46715
  markdown = htmlToMarkdown(doc.html);
46734
- log("[harmony-doc view] conversion result", {
46716
+ log("[harmony-doc-view] conversion result", {
46735
46717
  objectId,
46736
46718
  htmlLength: doc.html.length,
46737
46719
  markdownLength: markdown.length,
46738
46720
  markdownPreview: markdown.substring(0, 300)
46739
46721
  });
46740
46722
  if (!markdown.trim()) {
46741
- log("[harmony-doc view] markdown conversion produced empty output", {
46723
+ log("[harmony-doc-view] markdown conversion produced empty output", {
46742
46724
  objectId,
46743
46725
  htmlLength: doc.html.length
46744
46726
  });
@@ -46749,11 +46731,11 @@ async function executeView(url3, page) {
46749
46731
  try {
46750
46732
  breadcrumb = await resolveBreadcrumb("cn", doc.catalogName, objectId);
46751
46733
  } catch (e) {
46752
- log("[harmony-doc view] breadcrumb resolution failed", { objectId, error: String(e) });
46734
+ log("[harmony-doc-view] breadcrumb resolution failed", { objectId, error: String(e) });
46753
46735
  }
46754
46736
  }
46755
46737
  setCache(objectId, doc, markdown, breadcrumb);
46756
- log("[harmony-doc view] cache miss, fetched from network", { objectId });
46738
+ log("[harmony-doc-view] cache miss, fetched from network", { objectId });
46757
46739
  }
46758
46740
  const MAX_LENGTH = 3e4;
46759
46741
  const totalPages = Math.max(1, Math.ceil(markdown.length / MAX_LENGTH));
@@ -46784,9 +46766,9 @@ Source: ${url3}
46784
46766
  output += `
46785
46767
 
46786
46768
  ---
46787
- [Document continues \u2014 call with action='view' and page=${currentPage + 1} to read more]`;
46769
+ [Document continues \u2014 call with page=${currentPage + 1} to read more]`;
46788
46770
  }
46789
- log("[harmony-doc view] RETURN", {
46771
+ log("[harmony-doc-view] RETURN", {
46790
46772
  objectId,
46791
46773
  outputLength: output.length,
46792
46774
  chunkLength: chunk.length,
@@ -46804,7 +46786,7 @@ function createBuiltinTools(managers) {
46804
46786
  return {
46805
46787
  createHmTemplate: createHmTemplateTool(managers),
46806
46788
  skillSearch: skillSearchTool(managers),
46807
- "harmony-doc": harmonyDocTool(managers)
46789
+ "harmony-doc-view": harmonyDocViewTool(managers)
46808
46790
  };
46809
46791
  }
46810
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.11",
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",