@harmonyos-arkts/opencode-plugin 0.0.11 → 0.0.13
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/dist/index.js +262 -174
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5052,32 +5052,32 @@ var require_URL = __commonJS({
|
|
|
5052
5052
|
else
|
|
5053
5053
|
return basepath.substring(0, lastslash + 1) + refpath;
|
|
5054
5054
|
}
|
|
5055
|
-
function remove_dot_segments(
|
|
5056
|
-
if (!
|
|
5055
|
+
function remove_dot_segments(path9) {
|
|
5056
|
+
if (!path9) return path9;
|
|
5057
5057
|
var output = "";
|
|
5058
|
-
while (
|
|
5059
|
-
if (
|
|
5060
|
-
|
|
5058
|
+
while (path9.length > 0) {
|
|
5059
|
+
if (path9 === "." || path9 === "..") {
|
|
5060
|
+
path9 = "";
|
|
5061
5061
|
break;
|
|
5062
5062
|
}
|
|
5063
|
-
var twochars =
|
|
5064
|
-
var threechars =
|
|
5065
|
-
var fourchars =
|
|
5063
|
+
var twochars = path9.substring(0, 2);
|
|
5064
|
+
var threechars = path9.substring(0, 3);
|
|
5065
|
+
var fourchars = path9.substring(0, 4);
|
|
5066
5066
|
if (threechars === "../") {
|
|
5067
|
-
|
|
5067
|
+
path9 = path9.substring(3);
|
|
5068
5068
|
} else if (twochars === "./") {
|
|
5069
|
-
|
|
5069
|
+
path9 = path9.substring(2);
|
|
5070
5070
|
} else if (threechars === "/./") {
|
|
5071
|
-
|
|
5072
|
-
} else if (twochars === "/." &&
|
|
5073
|
-
|
|
5074
|
-
} else if (fourchars === "/../" || threechars === "/.." &&
|
|
5075
|
-
|
|
5071
|
+
path9 = "/" + path9.substring(3);
|
|
5072
|
+
} else if (twochars === "/." && path9.length === 2) {
|
|
5073
|
+
path9 = "/";
|
|
5074
|
+
} else if (fourchars === "/../" || threechars === "/.." && path9.length === 3) {
|
|
5075
|
+
path9 = "/" + path9.substring(4);
|
|
5076
5076
|
output = output.replace(/\/?[^\/]*$/, "");
|
|
5077
5077
|
} else {
|
|
5078
|
-
var segment =
|
|
5078
|
+
var segment = path9.match(/(\/?([^\/]*))/)[0];
|
|
5079
5079
|
output += segment;
|
|
5080
|
-
|
|
5080
|
+
path9 = path9.substring(segment.length);
|
|
5081
5081
|
}
|
|
5082
5082
|
}
|
|
5083
5083
|
return output;
|
|
@@ -18304,10 +18304,10 @@ function mergeDefs(...defs) {
|
|
|
18304
18304
|
function cloneDef(schema) {
|
|
18305
18305
|
return mergeDefs(schema._zod.def);
|
|
18306
18306
|
}
|
|
18307
|
-
function getElementAtPath(obj,
|
|
18308
|
-
if (!
|
|
18307
|
+
function getElementAtPath(obj, path9) {
|
|
18308
|
+
if (!path9)
|
|
18309
18309
|
return obj;
|
|
18310
|
-
return
|
|
18310
|
+
return path9.reduce((acc, key) => acc?.[key], obj);
|
|
18311
18311
|
}
|
|
18312
18312
|
function promiseAllObject(promisesObj) {
|
|
18313
18313
|
const keys = Object.keys(promisesObj);
|
|
@@ -18690,11 +18690,11 @@ function aborted(x, startIndex = 0) {
|
|
|
18690
18690
|
}
|
|
18691
18691
|
return false;
|
|
18692
18692
|
}
|
|
18693
|
-
function prefixIssues(
|
|
18693
|
+
function prefixIssues(path9, issues) {
|
|
18694
18694
|
return issues.map((iss) => {
|
|
18695
18695
|
var _a2;
|
|
18696
18696
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
18697
|
-
iss.path.unshift(
|
|
18697
|
+
iss.path.unshift(path9);
|
|
18698
18698
|
return iss;
|
|
18699
18699
|
});
|
|
18700
18700
|
}
|
|
@@ -18877,7 +18877,7 @@ function formatError(error92, mapper = (issue3) => issue3.message) {
|
|
|
18877
18877
|
}
|
|
18878
18878
|
function treeifyError(error92, mapper = (issue3) => issue3.message) {
|
|
18879
18879
|
const result = { errors: [] };
|
|
18880
|
-
const processError = (error93,
|
|
18880
|
+
const processError = (error93, path9 = []) => {
|
|
18881
18881
|
var _a2, _b;
|
|
18882
18882
|
for (const issue3 of error93.issues) {
|
|
18883
18883
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -18887,7 +18887,7 @@ function treeifyError(error92, mapper = (issue3) => issue3.message) {
|
|
|
18887
18887
|
} else if (issue3.code === "invalid_element") {
|
|
18888
18888
|
processError({ issues: issue3.issues }, issue3.path);
|
|
18889
18889
|
} else {
|
|
18890
|
-
const fullpath = [...
|
|
18890
|
+
const fullpath = [...path9, ...issue3.path];
|
|
18891
18891
|
if (fullpath.length === 0) {
|
|
18892
18892
|
result.errors.push(mapper(issue3));
|
|
18893
18893
|
continue;
|
|
@@ -18919,8 +18919,8 @@ function treeifyError(error92, mapper = (issue3) => issue3.message) {
|
|
|
18919
18919
|
}
|
|
18920
18920
|
function toDotPath(_path) {
|
|
18921
18921
|
const segs = [];
|
|
18922
|
-
const
|
|
18923
|
-
for (const seg of
|
|
18922
|
+
const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
18923
|
+
for (const seg of path9) {
|
|
18924
18924
|
if (typeof seg === "number")
|
|
18925
18925
|
segs.push(`[${seg}]`);
|
|
18926
18926
|
else if (typeof seg === "symbol")
|
|
@@ -30897,13 +30897,13 @@ function resolveRef(ref, ctx) {
|
|
|
30897
30897
|
if (!ref.startsWith("#")) {
|
|
30898
30898
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
30899
30899
|
}
|
|
30900
|
-
const
|
|
30901
|
-
if (
|
|
30900
|
+
const path9 = ref.slice(1).split("/").filter(Boolean);
|
|
30901
|
+
if (path9.length === 0) {
|
|
30902
30902
|
return ctx.rootSchema;
|
|
30903
30903
|
}
|
|
30904
30904
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
30905
|
-
if (
|
|
30906
|
-
const key =
|
|
30905
|
+
if (path9[0] === defsKey) {
|
|
30906
|
+
const key = path9[1];
|
|
30907
30907
|
if (!key || !ctx.defs[key]) {
|
|
30908
30908
|
throw new Error(`Reference not found: ${ref}`);
|
|
30909
30909
|
}
|
|
@@ -31572,6 +31572,7 @@ var HM_DESIGN = `
|
|
|
31572
31572
|
- Use the \`harmonyos-prd-design\` skill to create prd desgin documents
|
|
31573
31573
|
- Reference design patterns and best practices from the skill documentation
|
|
31574
31574
|
- Design documents should be stored in the project's \`.harmonyos/\` directory
|
|
31575
|
+
- After UX renders the prototype, write it as \`prototype.html\`, then call \`html_preview\` with \`filePath\` set to that file's local path so the frontend can open it reliably
|
|
31575
31576
|
`;
|
|
31576
31577
|
var HM_DEVELOP = `
|
|
31577
31578
|
### Development Phase
|
|
@@ -32938,10 +32939,10 @@ function mergeDefs2(...defs) {
|
|
|
32938
32939
|
function cloneDef2(schema) {
|
|
32939
32940
|
return mergeDefs2(schema._zod.def);
|
|
32940
32941
|
}
|
|
32941
|
-
function getElementAtPath2(obj,
|
|
32942
|
-
if (!
|
|
32942
|
+
function getElementAtPath2(obj, path9) {
|
|
32943
|
+
if (!path9)
|
|
32943
32944
|
return obj;
|
|
32944
|
-
return
|
|
32945
|
+
return path9.reduce((acc, key) => acc?.[key], obj);
|
|
32945
32946
|
}
|
|
32946
32947
|
function promiseAllObject2(promisesObj) {
|
|
32947
32948
|
const keys = Object.keys(promisesObj);
|
|
@@ -33302,11 +33303,11 @@ function aborted2(x, startIndex = 0) {
|
|
|
33302
33303
|
}
|
|
33303
33304
|
return false;
|
|
33304
33305
|
}
|
|
33305
|
-
function prefixIssues2(
|
|
33306
|
+
function prefixIssues2(path9, issues) {
|
|
33306
33307
|
return issues.map((iss) => {
|
|
33307
33308
|
var _a2;
|
|
33308
33309
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
33309
|
-
iss.path.unshift(
|
|
33310
|
+
iss.path.unshift(path9);
|
|
33310
33311
|
return iss;
|
|
33311
33312
|
});
|
|
33312
33313
|
}
|
|
@@ -33474,7 +33475,7 @@ function treeifyError2(error92, _mapper) {
|
|
|
33474
33475
|
return issue3.message;
|
|
33475
33476
|
};
|
|
33476
33477
|
const result = { errors: [] };
|
|
33477
|
-
const processError = (error93,
|
|
33478
|
+
const processError = (error93, path9 = []) => {
|
|
33478
33479
|
var _a2, _b;
|
|
33479
33480
|
for (const issue3 of error93.issues) {
|
|
33480
33481
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -33484,7 +33485,7 @@ function treeifyError2(error92, _mapper) {
|
|
|
33484
33485
|
} else if (issue3.code === "invalid_element") {
|
|
33485
33486
|
processError({ issues: issue3.issues }, issue3.path);
|
|
33486
33487
|
} else {
|
|
33487
|
-
const fullpath = [...
|
|
33488
|
+
const fullpath = [...path9, ...issue3.path];
|
|
33488
33489
|
if (fullpath.length === 0) {
|
|
33489
33490
|
result.errors.push(mapper(issue3));
|
|
33490
33491
|
continue;
|
|
@@ -33516,8 +33517,8 @@ function treeifyError2(error92, _mapper) {
|
|
|
33516
33517
|
}
|
|
33517
33518
|
function toDotPath2(_path) {
|
|
33518
33519
|
const segs = [];
|
|
33519
|
-
const
|
|
33520
|
-
for (const seg of
|
|
33520
|
+
const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
33521
|
+
for (const seg of path9) {
|
|
33521
33522
|
if (typeof seg === "number")
|
|
33522
33523
|
segs.push(`[${seg}]`);
|
|
33523
33524
|
else if (typeof seg === "symbol")
|
|
@@ -45333,37 +45334,6 @@ function convert_search_result(searchResults, prefix) {
|
|
|
45333
45334
|
});
|
|
45334
45335
|
}
|
|
45335
45336
|
|
|
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
45337
|
// src/tools/harmony-doc/api-client.ts
|
|
45368
45338
|
var BASE_URL = "https://svc-drcn.developer.huawei.com/community/servlet";
|
|
45369
45339
|
var HEADERS = {
|
|
@@ -45371,46 +45341,28 @@ var HEADERS = {
|
|
|
45371
45341
|
Referer: "https://developer.huawei.com/consumer/cn/doc/",
|
|
45372
45342
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
|
|
45373
45343
|
};
|
|
45374
|
-
var
|
|
45375
|
-
|
|
45376
|
-
|
|
45377
|
-
|
|
45378
|
-
|
|
45379
|
-
|
|
45380
|
-
|
|
45381
|
-
|
|
45382
|
-
|
|
45383
|
-
|
|
45384
|
-
|
|
45385
|
-
|
|
45386
|
-
|
|
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;
|
|
45344
|
+
var REQUEST_TIMEOUT_MS = 1e4;
|
|
45345
|
+
async function post(path9, body) {
|
|
45346
|
+
const controller = new AbortController();
|
|
45347
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
45348
|
+
try {
|
|
45349
|
+
const res = await fetch(BASE_URL + path9, {
|
|
45350
|
+
method: "POST",
|
|
45351
|
+
headers: HEADERS,
|
|
45352
|
+
body: JSON.stringify(body),
|
|
45353
|
+
signal: controller.signal
|
|
45354
|
+
});
|
|
45355
|
+
if (!res.ok) {
|
|
45356
|
+
throw new Error(`API request failed: ${res.status} ${res.statusText} for ${path9}`);
|
|
45411
45357
|
}
|
|
45358
|
+
const data = await res.json();
|
|
45359
|
+
if (data.code && data.code !== 0 && data.code !== "0") {
|
|
45360
|
+
throw new Error(`API error [${data.code}]: ${data.message} for ${path9}`);
|
|
45361
|
+
}
|
|
45362
|
+
return data;
|
|
45363
|
+
} finally {
|
|
45364
|
+
clearTimeout(timer);
|
|
45412
45365
|
}
|
|
45413
|
-
throw lastError ?? new Error("Request failed after retries");
|
|
45414
45366
|
}
|
|
45415
45367
|
var CATALOG_DISPLAY_NAMES = {
|
|
45416
45368
|
"harmonyos-guides": "\u6307\u5357",
|
|
@@ -45475,6 +45427,7 @@ async function searchDocs(keyword, maxResults = 10) {
|
|
|
45475
45427
|
title: r.name ?? "",
|
|
45476
45428
|
url: r.url ?? "",
|
|
45477
45429
|
excerpt: (r.content ?? "").replace(/\n/g, " ").substring(0, 200),
|
|
45430
|
+
excerptTruncated: (r.content ?? "").replace(/\n/g, " ").length > 200,
|
|
45478
45431
|
breadcrumb,
|
|
45479
45432
|
contentType: r.metaData?.type ?? 0,
|
|
45480
45433
|
timestamp: r.metaData?.timestamp ?? "",
|
|
@@ -45583,6 +45536,160 @@ function formatUpdateTime(utcTimeStr) {
|
|
|
45583
45536
|
return `${y}-${m}-${d} ${h}:${min}`;
|
|
45584
45537
|
}
|
|
45585
45538
|
|
|
45539
|
+
// src/tools/skill-search/skill-search-tool.ts
|
|
45540
|
+
function skillSearchTool(managers) {
|
|
45541
|
+
return tool({
|
|
45542
|
+
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.",
|
|
45543
|
+
args: {
|
|
45544
|
+
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'."),
|
|
45545
|
+
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'. "),
|
|
45546
|
+
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)
|
|
45547
|
+
},
|
|
45548
|
+
execute: async (args, context) => {
|
|
45549
|
+
const [localResults, docResults] = await Promise.all([
|
|
45550
|
+
searchSkill(args.skill_path, args.query, args.topK).catch(
|
|
45551
|
+
(e) => new Error(`Local search failed: ${e instanceof Error ? e.message : String(e)}`)
|
|
45552
|
+
),
|
|
45553
|
+
searchDocs(args.query, 5).catch(
|
|
45554
|
+
(e) => new Error(`Doc search failed: ${e instanceof Error ? e.message : String(e)}`)
|
|
45555
|
+
)
|
|
45556
|
+
]);
|
|
45557
|
+
const parts = [];
|
|
45558
|
+
if (localResults instanceof Error) {
|
|
45559
|
+
parts.push(`## Local Skill Results
|
|
45560
|
+
|
|
45561
|
+
${localResults.message}`);
|
|
45562
|
+
} else if (localResults.length === 0) {
|
|
45563
|
+
parts.push("## Local Skill Results\n\nNo matching skill documents found.");
|
|
45564
|
+
} else {
|
|
45565
|
+
const localInfo = localResults.map((r) => {
|
|
45566
|
+
const lines = [];
|
|
45567
|
+
if (r.experience_file_path) lines.push(`- experience path: ${r.experience_file_path}`);
|
|
45568
|
+
if (r.ets_file_path) lines.push(`- sample code path: ${r.ets_file_path}`);
|
|
45569
|
+
if (r.sdk_file_path) lines.push(`- sdk info path: ${r.sdk_file_path}`);
|
|
45570
|
+
return lines.join("\n");
|
|
45571
|
+
}).join("\n\n");
|
|
45572
|
+
parts.push(`## Local Skill Results
|
|
45573
|
+
|
|
45574
|
+
${localInfo}`);
|
|
45575
|
+
}
|
|
45576
|
+
if (docResults instanceof Error) {
|
|
45577
|
+
parts.push(`## Official Documentation
|
|
45578
|
+
|
|
45579
|
+
${docResults.message}`);
|
|
45580
|
+
} else if (docResults.length === 0) {
|
|
45581
|
+
parts.push("## Official Documentation\n\nNo matching official documents found.");
|
|
45582
|
+
} else {
|
|
45583
|
+
const docInfo = docResults.map((r, i) => {
|
|
45584
|
+
const lines = [];
|
|
45585
|
+
let title = r.title;
|
|
45586
|
+
if (r.subsection && r.subsection !== r.title) {
|
|
45587
|
+
title += ` #${r.subsection}`;
|
|
45588
|
+
}
|
|
45589
|
+
lines.push(`${i + 1}. **${title}**`);
|
|
45590
|
+
let displayUrl = r.url;
|
|
45591
|
+
if (r.anchorId) {
|
|
45592
|
+
displayUrl += `#${r.anchorId}`;
|
|
45593
|
+
}
|
|
45594
|
+
lines.push(` URL: ${displayUrl}`);
|
|
45595
|
+
if (r.excerpt) {
|
|
45596
|
+
const suffix = r.excerptTruncated ? "..." : "";
|
|
45597
|
+
lines.push(` ${r.excerpt}${suffix}`);
|
|
45598
|
+
}
|
|
45599
|
+
if (r.breadcrumb.length > 0) {
|
|
45600
|
+
lines.push(` \u6765\u81EA: ${r.breadcrumb.join(" > ")}`);
|
|
45601
|
+
}
|
|
45602
|
+
return lines.join("\n");
|
|
45603
|
+
}).join("\n\n");
|
|
45604
|
+
parts.push(
|
|
45605
|
+
`## Official Documentation
|
|
45606
|
+
|
|
45607
|
+
${docInfo}
|
|
45608
|
+
|
|
45609
|
+
Use harmony-doc-view with a URL above to read any official document in full.`
|
|
45610
|
+
);
|
|
45611
|
+
}
|
|
45612
|
+
return parts.join("\n\n");
|
|
45613
|
+
}
|
|
45614
|
+
});
|
|
45615
|
+
}
|
|
45616
|
+
|
|
45617
|
+
// src/tools/html-preview/html-preview-tool.ts
|
|
45618
|
+
import { access } from "fs/promises";
|
|
45619
|
+
import path7 from "path";
|
|
45620
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
|
|
45621
|
+
var PROTOTYPE_HTML_FILENAME = "prototype.html";
|
|
45622
|
+
function toFileUrl(filePath) {
|
|
45623
|
+
return pathToFileURL(path7.resolve(filePath)).href;
|
|
45624
|
+
}
|
|
45625
|
+
async function fileExists(filePath) {
|
|
45626
|
+
try {
|
|
45627
|
+
await access(filePath);
|
|
45628
|
+
return true;
|
|
45629
|
+
} catch {
|
|
45630
|
+
return false;
|
|
45631
|
+
}
|
|
45632
|
+
}
|
|
45633
|
+
function resolvePrototypePath(directory, input) {
|
|
45634
|
+
const trimmed = input.trim();
|
|
45635
|
+
if (!trimmed) {
|
|
45636
|
+
throw new Error("filePath is required");
|
|
45637
|
+
}
|
|
45638
|
+
if (trimmed.startsWith("file://")) {
|
|
45639
|
+
return fileURLToPath2(trimmed);
|
|
45640
|
+
}
|
|
45641
|
+
return path7.isAbsolute(trimmed) ? trimmed : path7.resolve(directory, trimmed);
|
|
45642
|
+
}
|
|
45643
|
+
async function resolvePrototypePreview(directory, filePathInput) {
|
|
45644
|
+
const filePath = resolvePrototypePath(directory, filePathInput);
|
|
45645
|
+
if (!await fileExists(filePath)) {
|
|
45646
|
+
throw new Error(
|
|
45647
|
+
`Prototype HTML not found: ${filePath}. Pass the exact local path where ${PROTOTYPE_HTML_FILENAME} was written.`
|
|
45648
|
+
);
|
|
45649
|
+
}
|
|
45650
|
+
return { url: toFileUrl(filePath), filePath };
|
|
45651
|
+
}
|
|
45652
|
+
function htmlPreviewTool(_managers) {
|
|
45653
|
+
return tool({
|
|
45654
|
+
description: "Send the UX prototype preview to the frontend. Call this immediately after writing prototype.html. You MUST pass filePath with the exact local path of the written file so the frontend can locate and preview it.",
|
|
45655
|
+
args: {
|
|
45656
|
+
filePath: tool.schema.string(
|
|
45657
|
+
"Local path to prototype.html. Use the same path from the write step, e.g. prototype.html, /Users/yanqing/coding/your-project/prototype.html, or file:///.../prototype.html."
|
|
45658
|
+
),
|
|
45659
|
+
title: tool.schema.optional(
|
|
45660
|
+
tool.schema.string("Optional title shown in the preview panel")
|
|
45661
|
+
)
|
|
45662
|
+
},
|
|
45663
|
+
execute: async (args, context) => {
|
|
45664
|
+
try {
|
|
45665
|
+
log("[html_preview]", { filePath: args.filePath, title: args.title });
|
|
45666
|
+
const { url: url3, filePath } = await resolvePrototypePreview(context.directory, args.filePath);
|
|
45667
|
+
const title = args.title?.trim() || "Prototype Preview";
|
|
45668
|
+
const metadata = {
|
|
45669
|
+
url: url3,
|
|
45670
|
+
filePath,
|
|
45671
|
+
fileName: path7.basename(filePath),
|
|
45672
|
+
title
|
|
45673
|
+
};
|
|
45674
|
+
context.metadata({
|
|
45675
|
+
title,
|
|
45676
|
+
metadata: {
|
|
45677
|
+
htmlPreview: metadata
|
|
45678
|
+
}
|
|
45679
|
+
});
|
|
45680
|
+
return [
|
|
45681
|
+
"Prototype preview sent to frontend.",
|
|
45682
|
+
`Title: ${title}`,
|
|
45683
|
+
`Local path: ${filePath}`,
|
|
45684
|
+
`Preview URL: ${url3}`
|
|
45685
|
+
].join("\n");
|
|
45686
|
+
} catch (e) {
|
|
45687
|
+
return `Error: ${e instanceof Error ? e.message : String(e)}`;
|
|
45688
|
+
}
|
|
45689
|
+
}
|
|
45690
|
+
});
|
|
45691
|
+
}
|
|
45692
|
+
|
|
45586
45693
|
// node_modules/turndown/lib/turndown.es.js
|
|
45587
45694
|
function extend3(destination) {
|
|
45588
45695
|
for (var i = 1; i < arguments.length; i++) {
|
|
@@ -46648,69 +46755,21 @@ function getCached(objectId) {
|
|
|
46648
46755
|
function setCache(objectId, doc, markdown, breadcrumb) {
|
|
46649
46756
|
docCache.set(objectId, { doc, markdown, breadcrumb, fetchedAt: Date.now() });
|
|
46650
46757
|
}
|
|
46651
|
-
function
|
|
46758
|
+
function harmonyDocViewTool(_managers) {
|
|
46652
46759
|
return tool({
|
|
46653
|
-
description: "HarmonyOS
|
|
46760
|
+
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
46761
|
args: {
|
|
46655
|
-
|
|
46656
|
-
|
|
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)
|
|
46762
|
+
url: tool.schema.string("Full URL of the document to view. Must be a developer.huawei.com URL, typically from skillSearch results."),
|
|
46763
|
+
page: tool.schema.number("Page number for long documents. Starts at 1.").min(1).default(1)
|
|
46660
46764
|
},
|
|
46661
46765
|
execute: async (args, _context) => {
|
|
46662
|
-
if (args.action === "search") {
|
|
46663
|
-
return executeSearch(args.query, args.maxResults);
|
|
46664
|
-
}
|
|
46665
46766
|
return executeView(args.url, args.page);
|
|
46666
46767
|
}
|
|
46667
46768
|
});
|
|
46668
46769
|
}
|
|
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
46770
|
async function executeView(url3, page) {
|
|
46709
|
-
if (!url3) {
|
|
46710
|
-
return "Error: 'url' is required when action is 'view'.";
|
|
46711
|
-
}
|
|
46712
46771
|
try {
|
|
46713
|
-
log("[harmony-doc
|
|
46772
|
+
log("[harmony-doc-view]", { url: url3, page });
|
|
46714
46773
|
const objectId = objectIdFromUrl(url3);
|
|
46715
46774
|
if (!objectId) {
|
|
46716
46775
|
return `Error: Cannot extract document ID from URL: ${url3}`;
|
|
@@ -46723,22 +46782,22 @@ async function executeView(url3, page) {
|
|
|
46723
46782
|
doc = cached3.doc;
|
|
46724
46783
|
markdown = cached3.markdown;
|
|
46725
46784
|
breadcrumb = cached3.breadcrumb;
|
|
46726
|
-
log("[harmony-doc
|
|
46785
|
+
log("[harmony-doc-view] cache hit", { objectId, page });
|
|
46727
46786
|
} else {
|
|
46728
46787
|
doc = await fetchDocument(objectId);
|
|
46729
46788
|
if (!doc.html) {
|
|
46730
|
-
log("[harmony-doc
|
|
46789
|
+
log("[harmony-doc-view] API returned empty HTML", { objectId, title: doc.title });
|
|
46731
46790
|
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
46791
|
}
|
|
46733
46792
|
markdown = htmlToMarkdown(doc.html);
|
|
46734
|
-
log("[harmony-doc
|
|
46793
|
+
log("[harmony-doc-view] conversion result", {
|
|
46735
46794
|
objectId,
|
|
46736
46795
|
htmlLength: doc.html.length,
|
|
46737
46796
|
markdownLength: markdown.length,
|
|
46738
46797
|
markdownPreview: markdown.substring(0, 300)
|
|
46739
46798
|
});
|
|
46740
46799
|
if (!markdown.trim()) {
|
|
46741
|
-
log("[harmony-doc
|
|
46800
|
+
log("[harmony-doc-view] markdown conversion produced empty output", {
|
|
46742
46801
|
objectId,
|
|
46743
46802
|
htmlLength: doc.html.length
|
|
46744
46803
|
});
|
|
@@ -46749,11 +46808,11 @@ async function executeView(url3, page) {
|
|
|
46749
46808
|
try {
|
|
46750
46809
|
breadcrumb = await resolveBreadcrumb("cn", doc.catalogName, objectId);
|
|
46751
46810
|
} catch (e) {
|
|
46752
|
-
log("[harmony-doc
|
|
46811
|
+
log("[harmony-doc-view] breadcrumb resolution failed", { objectId, error: String(e) });
|
|
46753
46812
|
}
|
|
46754
46813
|
}
|
|
46755
46814
|
setCache(objectId, doc, markdown, breadcrumb);
|
|
46756
|
-
log("[harmony-doc
|
|
46815
|
+
log("[harmony-doc-view] cache miss, fetched from network", { objectId });
|
|
46757
46816
|
}
|
|
46758
46817
|
const MAX_LENGTH = 3e4;
|
|
46759
46818
|
const totalPages = Math.max(1, Math.ceil(markdown.length / MAX_LENGTH));
|
|
@@ -46784,9 +46843,9 @@ Source: ${url3}
|
|
|
46784
46843
|
output += `
|
|
46785
46844
|
|
|
46786
46845
|
---
|
|
46787
|
-
[Document continues \u2014 call with
|
|
46846
|
+
[Document continues \u2014 call with page=${currentPage + 1} to read more]`;
|
|
46788
46847
|
}
|
|
46789
|
-
log("[harmony-doc
|
|
46848
|
+
log("[harmony-doc-view] RETURN", {
|
|
46790
46849
|
objectId,
|
|
46791
46850
|
outputLength: output.length,
|
|
46792
46851
|
chunkLength: chunk.length,
|
|
@@ -46804,7 +46863,8 @@ function createBuiltinTools(managers) {
|
|
|
46804
46863
|
return {
|
|
46805
46864
|
createHmTemplate: createHmTemplateTool(managers),
|
|
46806
46865
|
skillSearch: skillSearchTool(managers),
|
|
46807
|
-
|
|
46866
|
+
html_preview: htmlPreviewTool(managers),
|
|
46867
|
+
"harmony-doc-view": harmonyDocViewTool(managers)
|
|
46808
46868
|
};
|
|
46809
46869
|
}
|
|
46810
46870
|
|
|
@@ -46928,11 +46988,11 @@ Diff.prototype = {
|
|
|
46928
46988
|
}
|
|
46929
46989
|
}
|
|
46930
46990
|
},
|
|
46931
|
-
addToPath: function addToPath(
|
|
46932
|
-
var last =
|
|
46991
|
+
addToPath: function addToPath(path9, added, removed, oldPosInc, options) {
|
|
46992
|
+
var last = path9.lastComponent;
|
|
46933
46993
|
if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
|
|
46934
46994
|
return {
|
|
46935
|
-
oldPos:
|
|
46995
|
+
oldPos: path9.oldPos + oldPosInc,
|
|
46936
46996
|
lastComponent: {
|
|
46937
46997
|
count: last.count + 1,
|
|
46938
46998
|
added,
|
|
@@ -46942,7 +47002,7 @@ Diff.prototype = {
|
|
|
46942
47002
|
};
|
|
46943
47003
|
} else {
|
|
46944
47004
|
return {
|
|
46945
|
-
oldPos:
|
|
47005
|
+
oldPos: path9.oldPos + oldPosInc,
|
|
46946
47006
|
lastComponent: {
|
|
46947
47007
|
count: 1,
|
|
46948
47008
|
added,
|
|
@@ -47379,6 +47439,9 @@ arrayDiff.join = arrayDiff.removeEmpty = function(value) {
|
|
|
47379
47439
|
return value;
|
|
47380
47440
|
};
|
|
47381
47441
|
|
|
47442
|
+
// src/hooks/tool-hooks.ts
|
|
47443
|
+
import path8 from "path";
|
|
47444
|
+
|
|
47382
47445
|
// src/compress/compile-hvigorw.ts
|
|
47383
47446
|
function processHvigorwCompileInfo(input, output) {
|
|
47384
47447
|
if (input.tool !== "bash") return;
|
|
@@ -47420,6 +47483,7 @@ function createToolHooks(sessionManager, projectDir) {
|
|
|
47420
47483
|
"tool.execute.after": async (input, output) => {
|
|
47421
47484
|
processHvigorwCompileInfo(input, output);
|
|
47422
47485
|
injectAiCodeChange(input, output);
|
|
47486
|
+
await injectHtmlPreview(input, output, projectDir);
|
|
47423
47487
|
await updateEtsCount(sessionManager, projectDir, input);
|
|
47424
47488
|
}
|
|
47425
47489
|
};
|
|
@@ -47445,6 +47509,30 @@ async function updateEtsCount(sessionManager, projectDir, input) {
|
|
|
47445
47509
|
}
|
|
47446
47510
|
}
|
|
47447
47511
|
}
|
|
47512
|
+
async function injectHtmlPreview(input, output, projectDir) {
|
|
47513
|
+
if (input.tool !== "html_preview") return;
|
|
47514
|
+
try {
|
|
47515
|
+
const existing = output.metadata.htmlPreview;
|
|
47516
|
+
if (existing?.url) {
|
|
47517
|
+
output.title = existing.title?.trim() || "Prototype Preview";
|
|
47518
|
+
return;
|
|
47519
|
+
}
|
|
47520
|
+
const filePathArg = input.args?.filePath;
|
|
47521
|
+
if (!filePathArg) return;
|
|
47522
|
+
const title = input.args?.title?.trim() || "Prototype Preview";
|
|
47523
|
+
const { url: url3, filePath } = await resolvePrototypePreview(projectDir, filePathArg);
|
|
47524
|
+
output.title = title;
|
|
47525
|
+
output.metadata.htmlPreview = {
|
|
47526
|
+
url: url3,
|
|
47527
|
+
filePath,
|
|
47528
|
+
fileName: path8.basename(filePath),
|
|
47529
|
+
title
|
|
47530
|
+
};
|
|
47531
|
+
log("htmlPreview injected", { url: url3, title });
|
|
47532
|
+
} catch (e) {
|
|
47533
|
+
log("htmlPreview after hook error", { tool: input.tool, error: String(e) });
|
|
47534
|
+
}
|
|
47535
|
+
}
|
|
47448
47536
|
function injectAiCodeChange(input, output) {
|
|
47449
47537
|
try {
|
|
47450
47538
|
if (input.tool === "write") {
|
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.13",
|
|
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",
|