deveco-harness 0.1.0

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 (135) hide show
  1. package/README.en.md +71 -0
  2. package/README.md +71 -0
  3. package/deveco-harness.example.jsonc +32 -0
  4. package/dist/index.js +758 -0
  5. package/package.json +48 -0
  6. package/prompts/build.txt +164 -0
  7. package/prompts/plan.txt +118 -0
  8. package/skills/arkts-error-fixes/README.md +151 -0
  9. package/skills/arkts-error-fixes/SKILL.md +150 -0
  10. package/skills/arkts-error-fixes/assets/AnyTypeError.ets +269 -0
  11. package/skills/arkts-error-fixes/assets/AppStorageError.ets +22 -0
  12. package/skills/arkts-error-fixes/assets/ArrowFunctionConversionError.ets +42 -0
  13. package/skills/arkts-error-fixes/assets/AvoidAreaTypeError.ets +43 -0
  14. package/skills/arkts-error-fixes/assets/BreakpointTypeError.ets +71 -0
  15. package/skills/arkts-error-fixes/assets/CatchClauseTypeError.ets +54 -0
  16. package/skills/arkts-error-fixes/assets/ColorConsistencyError.ets +88 -0
  17. package/skills/arkts-error-fixes/assets/ColorPropertyError.ets +39 -0
  18. package/skills/arkts-error-fixes/assets/ContextTypeError.ets +105 -0
  19. package/skills/arkts-error-fixes/assets/DecoratorStateError.ets +64 -0
  20. package/skills/arkts-error-fixes/assets/DisplayListenerTypeError.ets +129 -0
  21. package/skills/arkts-error-fixes/assets/DuplicateEntryError.ets +35 -0
  22. package/skills/arkts-error-fixes/assets/ESObjectTypeError.ets +137 -0
  23. package/skills/arkts-error-fixes/assets/FontColorPropertyError.ets +42 -0
  24. package/skills/arkts-error-fixes/assets/FunctionReturnTypeError.ets +41 -0
  25. package/skills/arkts-error-fixes/assets/IDataSourceError.ets +79 -0
  26. package/skills/arkts-error-fixes/assets/ImplementationNotAllowedError.ets +38 -0
  27. package/skills/arkts-error-fixes/assets/InterfaceMethodSignatureError.ets +43 -0
  28. package/skills/arkts-error-fixes/assets/NotificationError.ets +34 -0
  29. package/skills/arkts-error-fixes/assets/ObjectLiteralInterfaceError.ets +49 -0
  30. package/skills/arkts-error-fixes/assets/ObjectLiteralTypeError.ets +54 -0
  31. package/skills/arkts-error-fixes/assets/ObjectSpreadError.ets +29 -0
  32. package/skills/arkts-error-fixes/assets/PossiblyNullError.ets +32 -0
  33. package/skills/arkts-error-fixes/assets/ResourceConversionError.ets +159 -0
  34. package/skills/arkts-error-fixes/assets/StandaloneFunctionContext.ets +116 -0
  35. package/skills/arkts-error-fixes/assets/StandaloneFunctionError.ets +85 -0
  36. package/skills/arkts-error-fixes/assets/StorageLinkDefaultError.ets +41 -0
  37. package/skills/arkts-error-fixes/assets/TitleButtonRectTypeError.ets +86 -0
  38. package/skills/arkts-error-fixes/assets/UnusedVariableWarning.ets +102 -0
  39. package/skills/arkts-error-fixes/assets/UtilityTypeError.ets +176 -0
  40. package/skills/arkts-error-fixes/assets/WindowRectSizeError.ets +65 -0
  41. package/skills/arkts-error-fixes/assets/WindowTypeError.ets +37 -0
  42. package/skills/arkts-error-fixes/reference/any_type_errors.md +433 -0
  43. package/skills/arkts-error-fixes/reference/appstorage_errors.md +95 -0
  44. package/skills/arkts-error-fixes/reference/arrow_function_conversion_errors.md +163 -0
  45. package/skills/arkts-error-fixes/reference/avoid_area_type_errors.md +59 -0
  46. package/skills/arkts-error-fixes/reference/breakpoint_type_errors.md +122 -0
  47. package/skills/arkts-error-fixes/reference/catch_clause_type_errors.md +89 -0
  48. package/skills/arkts-error-fixes/reference/color_consistency_errors.md +209 -0
  49. package/skills/arkts-error-fixes/reference/color_property_errors.md +136 -0
  50. package/skills/arkts-error-fixes/reference/context_type_errors.md +260 -0
  51. package/skills/arkts-error-fixes/reference/decorator_state_errors.md +109 -0
  52. package/skills/arkts-error-fixes/reference/display_listener_type_errors.md +149 -0
  53. package/skills/arkts-error-fixes/reference/duplicate_entry_errors.md +193 -0
  54. package/skills/arkts-error-fixes/reference/esobject_type_errors.md +110 -0
  55. package/skills/arkts-error-fixes/reference/fontcolor_property_errors.md +104 -0
  56. package/skills/arkts-error-fixes/reference/function_return_type_errors.md +194 -0
  57. package/skills/arkts-error-fixes/reference/idata_source_errors.md +77 -0
  58. package/skills/arkts-error-fixes/reference/implementation_not_allowed_errors.md +191 -0
  59. package/skills/arkts-error-fixes/reference/interface_method_signature_errors.md +143 -0
  60. package/skills/arkts-error-fixes/reference/notification_errors.md +110 -0
  61. package/skills/arkts-error-fixes/reference/object_literal_interface_errors.md +156 -0
  62. package/skills/arkts-error-fixes/reference/object_literal_type_errors.md +110 -0
  63. package/skills/arkts-error-fixes/reference/object_spread_errors.md +105 -0
  64. package/skills/arkts-error-fixes/reference/possibly_null_errors.md +156 -0
  65. package/skills/arkts-error-fixes/reference/resource_conversion_errors.md +154 -0
  66. package/skills/arkts-error-fixes/reference/standalone_function_errors.md +68 -0
  67. package/skills/arkts-error-fixes/reference/storage_link_default_errors.md +124 -0
  68. package/skills/arkts-error-fixes/reference/title_button_rect_type_errors.md +167 -0
  69. package/skills/arkts-error-fixes/reference/unused_variable_warnings.md +295 -0
  70. package/skills/arkts-error-fixes/reference/utility_type_errors.md +290 -0
  71. package/skills/arkts-error-fixes/reference/window_rect_size_errors.md +70 -0
  72. package/skills/arkts-error-fixes/reference/window_type_errors.md +95 -0
  73. package/skills/arkts-grammar-standards/SKILL.md +71 -0
  74. package/skills/arkts-grammar-standards/references/basic-syntax.md +103 -0
  75. package/skills/arkts-grammar-standards/references/restrictions.md +100 -0
  76. package/skills/arkts-grammar-standards/references/topic-aliases.json +93 -0
  77. package/skills/arkts-grammar-standards/references/ts-diff.md +108 -0
  78. package/skills/arkts-runtime-fix/SKILL.md +152 -0
  79. package/skills/arkts-runtime-fix/SKILL_CN.md +123 -0
  80. package/skills/arkts-runtime-fix/evals/evals.json +17 -0
  81. package/skills/arkts-runtime-fix/scripts/collect-hilog.mjs +116 -0
  82. package/skills/arkts-runtime-fix/scripts/collect-hilog.ts +115 -0
  83. package/skills/arkts-runtime-fix/scripts/fetch-faultlog.mjs +117 -0
  84. package/skills/arkts-runtime-fix/scripts/fetch-faultlog.ts +116 -0
  85. package/skills/arkts-runtime-fix/scripts/jscrash-report.mjs +149 -0
  86. package/skills/arkts-runtime-fix/scripts/parse-jscrash-log.mjs +126 -0
  87. package/skills/arkts-runtime-fix/scripts/parse-jscrash-log.ts +126 -0
  88. package/skills/arkts-runtime-fix/scripts/probe-faultlogger.mjs +160 -0
  89. package/skills/arkts-runtime-fix/scripts/probe-faultlogger.ts +160 -0
  90. package/skills/arkts-runtime-fix/scripts/shared/hdc.mjs +107 -0
  91. package/skills/arkts-runtime-fix/scripts/shared/hdc.ts +58 -0
  92. package/skills/arkts-runtime-fix/scripts/shared/jscrash-faultlogger.mjs +143 -0
  93. package/skills/arkts-runtime-fix/scripts/shared/jscrash-faultlogger.ts +148 -0
  94. package/skills/arkts-runtime-fix/scripts/shared/jscrash-parse.mjs +389 -0
  95. package/skills/arkts-runtime-fix/scripts/shared/jscrash-parse.ts +409 -0
  96. package/skills/arkts-runtime-fix/scripts/shared/utils.mjs +27 -0
  97. package/skills/arkts-runtime-fix/scripts/shared/utils.ts +27 -0
  98. package/skills/arkui-knowledge/SKILL.md +90 -0
  99. package/skills/arkui-knowledge/references/api-guardrails.md +60 -0
  100. package/skills/arkui-knowledge/references/common-mistakes.md +119 -0
  101. package/skills/arkui-knowledge/references/component-cookbook.md +135 -0
  102. package/skills/arkui-knowledge/references/ui-quality-checklist.md +36 -0
  103. package/skills/deveco-create-project/SKILL.md +144 -0
  104. package/skills/deveco-create-project/application/AppScope/app.json5 +10 -0
  105. package/skills/deveco-create-project/application/AppScope/resources/base/element/string.json +8 -0
  106. package/skills/deveco-create-project/application/AppScope/resources/base/media/background.png +0 -0
  107. package/skills/deveco-create-project/application/AppScope/resources/base/media/foreground.png +0 -0
  108. package/skills/deveco-create-project/application/AppScope/resources/base/media/layered_image.json +7 -0
  109. package/skills/deveco-create-project/application/build-profile.json5 +42 -0
  110. package/skills/deveco-create-project/application/code-linter.json5 +32 -0
  111. package/skills/deveco-create-project/application/entry/build-profile.json5 +33 -0
  112. package/skills/deveco-create-project/application/entry/hvigorfile.ts +7 -0
  113. package/skills/deveco-create-project/application/entry/obfuscation-rules.txt +23 -0
  114. package/skills/deveco-create-project/application/entry/oh-package.json5 +10 -0
  115. package/skills/deveco-create-project/application/entry/src/main/ets/entryability/EntryAbility.ets +63 -0
  116. package/skills/deveco-create-project/application/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +31 -0
  117. package/skills/deveco-create-project/application/entry/src/main/ets/pages/Index.ets +38 -0
  118. package/skills/deveco-create-project/application/entry/src/main/module.json5 +50 -0
  119. package/skills/deveco-create-project/application/entry/src/main/resources/base/element/color.json +8 -0
  120. package/skills/deveco-create-project/application/entry/src/main/resources/base/element/float.json +8 -0
  121. package/skills/deveco-create-project/application/entry/src/main/resources/base/element/string.json +16 -0
  122. package/skills/deveco-create-project/application/entry/src/main/resources/base/media/background.png +0 -0
  123. package/skills/deveco-create-project/application/entry/src/main/resources/base/media/foreground.png +0 -0
  124. package/skills/deveco-create-project/application/entry/src/main/resources/base/media/layered_image.json +7 -0
  125. package/skills/deveco-create-project/application/entry/src/main/resources/base/media/startIcon.png +0 -0
  126. package/skills/deveco-create-project/application/entry/src/main/resources/base/profile/backup_config.json +3 -0
  127. package/skills/deveco-create-project/application/entry/src/main/resources/base/profile/main_pages.json +5 -0
  128. package/skills/deveco-create-project/application/entry/src/main/resources/dark/element/color.json +8 -0
  129. package/skills/deveco-create-project/application/hvigor/hvigor-config.json5 +23 -0
  130. package/skills/deveco-create-project/application/hvigorfile.ts +7 -0
  131. package/skills/deveco-create-project/application/oh-package.json5 +10 -0
  132. package/skills/deveco-create-project/scripts/copy-template.mjs +216 -0
  133. package/skills/deveco-create-project/scripts/copy-template.ts +214 -0
  134. package/skills/deveco-create-project/scripts/detect-sdk.mjs +82 -0
  135. package/skills/deveco-create-project/scripts/detect-sdk.ts +95 -0
package/dist/index.js ADDED
@@ -0,0 +1,758 @@
1
+ // src/agents.ts
2
+ import { readFileSync } from "node:fs";
3
+ import path2 from "node:path";
4
+
5
+ // src/plugin-root.ts
6
+ import { existsSync } from "node:fs";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ function packageRoot(fromUrl = import.meta.url) {
10
+ let current = path.dirname(fileURLToPath(fromUrl));
11
+ while (true) {
12
+ if (existsSync(path.join(current, "package.json")))
13
+ return current;
14
+ const parent = path.dirname(current);
15
+ if (parent === current)
16
+ return path.dirname(fileURLToPath(fromUrl));
17
+ current = parent;
18
+ }
19
+ }
20
+
21
+ // src/agents.ts
22
+ function readPrompt(name) {
23
+ return readFileSync(path2.join(packageRoot(), "prompts", name), "utf8");
24
+ }
25
+ var BUILD_PROMPT = readPrompt("build.txt");
26
+ var PLAN_PROMPT = readPrompt("plan.txt");
27
+ var MIGRATED_AGENT_OVERRIDES = {
28
+ build: {
29
+ mode: "primary",
30
+ description: "The default DevEco Code agent. Implements HarmonyOS changes and verifies them with DevEco tools.",
31
+ prompt: BUILD_PROMPT,
32
+ permission: {
33
+ question: "allow",
34
+ plan_enter: "ask",
35
+ plan_write: "deny",
36
+ verify_ui: "ask",
37
+ save_ui_screenshot: "ask",
38
+ get_ui_verification_log: "ask",
39
+ arkts_knowledge_search: "allow",
40
+ build_project: "allow",
41
+ check_ets_files: "allow",
42
+ start_app: "allow",
43
+ hdc_log: "allow",
44
+ switch_cwd: "allow"
45
+ }
46
+ },
47
+ plan: {
48
+ mode: "primary",
49
+ description: "Plan mode for ArkTS and HarmonyOS work. Disallows edits and execution tools.",
50
+ prompt: PLAN_PROMPT,
51
+ permission: {
52
+ question: "allow",
53
+ plan_exit: "ask",
54
+ plan_write: "allow",
55
+ bash: "deny",
56
+ build_project: "deny",
57
+ check_ets_files: "deny",
58
+ perform_ui_action: "deny",
59
+ get_app_ui_tree: "deny",
60
+ start_app: "deny",
61
+ hdc_log: "deny",
62
+ switch_cwd: "deny",
63
+ arkts_knowledge_search: "deny",
64
+ edit: "deny"
65
+ }
66
+ }
67
+ };
68
+ function mergeAgent(base, override) {
69
+ return {
70
+ ...base,
71
+ ...override,
72
+ permission: {
73
+ ...base.permission,
74
+ ...override?.permission
75
+ }
76
+ };
77
+ }
78
+ function applyAgentConfig(config, pluginConfig) {
79
+ const existing = config.agent ?? {};
80
+ const agents = { ...existing };
81
+ const overrides = pluginConfig.agents ?? {};
82
+ for (const [name, agent] of Object.entries(MIGRATED_AGENT_OVERRIDES)) {
83
+ agents[name] = mergeAgent(agent, overrides[name]);
84
+ }
85
+ for (const [name, agent] of Object.entries(overrides)) {
86
+ if (name in MIGRATED_AGENT_OVERRIDES)
87
+ continue;
88
+ agents[name] = agent;
89
+ }
90
+ config.agent = agents;
91
+ }
92
+
93
+ // src/config.ts
94
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
95
+ import os from "node:os";
96
+ import path3 from "node:path";
97
+ import { parse } from "jsonc-parser";
98
+ var CONFIG_FILES = ["deveco-harness.jsonc", "deveco-harness.json"];
99
+ function mergeConfig(base, next) {
100
+ return {
101
+ ...base,
102
+ ...next,
103
+ agents: {
104
+ ...base.agents,
105
+ ...next.agents
106
+ },
107
+ tools: {
108
+ ...base.tools,
109
+ ...next.tools
110
+ },
111
+ skills: {
112
+ ...base.skills,
113
+ ...next.skills,
114
+ paths: [...base.skills?.paths ?? [], ...next.skills?.paths ?? []]
115
+ }
116
+ };
117
+ }
118
+ function readConfigFile(file) {
119
+ const errors = [];
120
+ const value = parse(readFileSync2(file, "utf8"), errors, {
121
+ allowTrailingComma: true,
122
+ disallowComments: false
123
+ });
124
+ if (errors.length > 0) {
125
+ console.warn(`[deveco-harness] ignored invalid config ${file}: ${errors[0]?.error}`);
126
+ return {};
127
+ }
128
+ if (!value || typeof value !== "object" || Array.isArray(value))
129
+ return {};
130
+ return value;
131
+ }
132
+ function candidateFiles(directory) {
133
+ const globalDir = path3.join(os.homedir(), ".config", "opencode");
134
+ const projectDir = path3.join(directory, ".opencode");
135
+ return [
136
+ ...CONFIG_FILES.map((file) => path3.join(globalDir, file)),
137
+ ...CONFIG_FILES.map((file) => path3.join(directory, file)),
138
+ ...CONFIG_FILES.map((file) => path3.join(projectDir, file))
139
+ ];
140
+ }
141
+ function loadDevecoAgentConfig(directory) {
142
+ let result = {};
143
+ for (const file of candidateFiles(directory)) {
144
+ if (!existsSync2(file))
145
+ continue;
146
+ result = mergeConfig(result, readConfigFile(file));
147
+ }
148
+ return result;
149
+ }
150
+ function resolveConfigPath(input, directory) {
151
+ if (input.startsWith("~/"))
152
+ return path3.join(os.homedir(), input.slice(2));
153
+ if (path3.isAbsolute(input))
154
+ return input;
155
+ return path3.resolve(directory, input);
156
+ }
157
+
158
+ // src/skills.ts
159
+ import { existsSync as existsSync3 } from "node:fs";
160
+ import path4 from "node:path";
161
+ function appendUnique(target, value) {
162
+ if (!target.includes(value))
163
+ target.push(value);
164
+ }
165
+ function applySkillConfig(hostConfig, pluginConfig, directory) {
166
+ if (pluginConfig.skills?.enabled === false)
167
+ return;
168
+ const skills = hostConfig.skills ?? {};
169
+ const paths = [...skills.paths ?? []];
170
+ const bundled = path4.join(packageRoot(), "skills");
171
+ if (existsSync3(bundled))
172
+ appendUnique(paths, bundled);
173
+ for (const item of pluginConfig.skills?.paths ?? []) {
174
+ appendUnique(paths, resolveConfigPath(item, directory));
175
+ }
176
+ hostConfig.skills = {
177
+ ...skills,
178
+ paths
179
+ };
180
+ }
181
+
182
+ // src/tool/arkts-check.ts
183
+ import path6 from "node:path";
184
+ import { tool } from "@opencode-ai/plugin";
185
+
186
+ // src/tool/deveco-home.ts
187
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
188
+ import os2 from "node:os";
189
+ import path5 from "node:path";
190
+ function binary(name) {
191
+ return process.platform === "win32" ? `${name}.exe` : name;
192
+ }
193
+ function candidatesFrom(home) {
194
+ const normalized = home.trim();
195
+ if (!normalized)
196
+ return [];
197
+ const candidates = [normalized];
198
+ if (process.platform !== "win32" && path5.basename(normalized) !== "Contents") {
199
+ candidates.push(path5.join(normalized, "Contents"));
200
+ }
201
+ return candidates;
202
+ }
203
+ function defaults() {
204
+ if (process.platform === "darwin")
205
+ return ["/Applications/DevEco-Studio.app"];
206
+ if (process.platform === "linux") {
207
+ const home2 = os2.homedir();
208
+ return [path5.join(home2, "devecostudio"), path5.join(home2, "DevEco-Studio")];
209
+ }
210
+ const home = process.env.USERPROFILE || os2.homedir();
211
+ return [
212
+ "D:\\DevEco Studio",
213
+ "C:\\Program Files\\Huawei\\DevEco Studio",
214
+ "C:\\Program Files\\DevEco Studio",
215
+ "C:\\Program Files (x86)\\DevEco Studio",
216
+ path5.join(home, "DevEco Studio")
217
+ ];
218
+ }
219
+ function nodePath(home) {
220
+ return process.platform === "win32" ? path5.join(home, "tools", "node", "node.exe") : path5.join(home, "tools", "node", "bin", "node");
221
+ }
222
+ function hdcPath(home) {
223
+ return path5.join(home, "sdk", "default", "openharmony", "toolchains", binary("hdc"));
224
+ }
225
+ function productInfoPath(home) {
226
+ if (process.platform === "darwin")
227
+ return path5.join(home, "Resources", "product-info.json");
228
+ return path5.join(home, "product-info.json");
229
+ }
230
+ function hasDevEcoShape(home) {
231
+ return existsSync4(nodePath(home)) && existsSync4(productInfoPath(home));
232
+ }
233
+ function savedDevEcoHomeFile() {
234
+ return path5.join(os2.homedir(), ".local", "state", "opencode", "deveco-home.json");
235
+ }
236
+ function loadSavedDevEcoHome() {
237
+ try {
238
+ const data = JSON.parse(readFileSync3(savedDevEcoHomeFile(), "utf8"));
239
+ return typeof data.deveco_home === "string" ? data.deveco_home : undefined;
240
+ } catch {
241
+ return;
242
+ }
243
+ }
244
+ function findDevEcoHome() {
245
+ const raw = [
246
+ process.env.DEVECO_HOME,
247
+ loadSavedDevEcoHome(),
248
+ ...defaults()
249
+ ].filter((item) => typeof item === "string" && item.trim().length > 0);
250
+ for (const item of raw) {
251
+ for (const candidate of candidatesFrom(item)) {
252
+ if (hasDevEcoShape(candidate))
253
+ return candidate;
254
+ }
255
+ }
256
+ }
257
+
258
+ // src/tool/process.ts
259
+ import { spawn } from "node:child_process";
260
+
261
+ // src/tool/session-cwd.ts
262
+ var sessionCwd = new Map;
263
+ function setSessionCwd(sessionID, cwd) {
264
+ const id = sessionID.trim();
265
+ const dir = cwd.trim();
266
+ if (!id || !dir)
267
+ return;
268
+ sessionCwd.set(id, dir);
269
+ }
270
+ function getSessionCwd(sessionID) {
271
+ if (!sessionID)
272
+ return;
273
+ return sessionCwd.get(sessionID);
274
+ }
275
+
276
+ // src/tool/process.ts
277
+ function contextDirectory(context) {
278
+ return getSessionCwd(context.sessionID) ?? context.directory;
279
+ }
280
+ function runCommand(command, args, cwd, signal, env = process.env) {
281
+ return new Promise((resolve) => {
282
+ const child = spawn(command, args, {
283
+ cwd,
284
+ env,
285
+ windowsHide: true
286
+ });
287
+ let stdout = "";
288
+ let stderr = "";
289
+ const abort = () => child.kill();
290
+ if (signal.aborted)
291
+ abort();
292
+ signal.addEventListener("abort", abort, { once: true });
293
+ child.stdout.on("data", (chunk) => {
294
+ stdout += chunk.toString();
295
+ });
296
+ child.stderr.on("data", (chunk) => {
297
+ stderr += chunk.toString();
298
+ });
299
+ child.on("error", (error) => {
300
+ signal.removeEventListener("abort", abort);
301
+ resolve({
302
+ code: 1,
303
+ stdout,
304
+ stderr: `${stderr}${stderr ? `
305
+ ` : ""}${error.message}`
306
+ });
307
+ });
308
+ child.on("close", (code) => {
309
+ signal.removeEventListener("abort", abort);
310
+ resolve({ code, stdout, stderr });
311
+ });
312
+ });
313
+ }
314
+
315
+ // src/tool/arkts-check.ts
316
+ function createArktsCheckTool() {
317
+ return tool({
318
+ description: "Run ArkTS strict-mode static syntax/type check on .ets files. " + "Detects compiler-level violations such as `arkts-no-standalone-this`, `arkts-no-obj-literals-as-types`, and other ArkTS spec issues. " + "Returns structured diagnostics by file, line, column, and rule. " + "Call this after writing or editing any .ets file, before running build_project — catches most ArkTS errors faster than a full build. " + "Do NOT call for non-.ets source files, or when DevEco Studio is not installed locally.",
319
+ args: {
320
+ files: tool.schema.array(tool.schema.string()).optional().nullable().describe("Specific .ets files to check (absolute paths or paths relative to project root). " + "If omitted, all .ets files under entry/src/main/ets are checked.")
321
+ },
322
+ async execute(args, context) {
323
+ const projectPath = contextDirectory(context);
324
+ await context.ask({
325
+ permission: "arkts_check",
326
+ patterns: ["arkts_check"],
327
+ always: [],
328
+ metadata: { project: projectPath }
329
+ });
330
+ const home = findDevEcoHome();
331
+ if (!home) {
332
+ throw new Error("DevEco Studio not found. Set the DEVECO_HOME environment variable and retry.");
333
+ }
334
+ const node = nodePath(home);
335
+ const script = path6.join(import.meta.dirname, "arkts-check.cjs");
336
+ const cmdArgs = ["--project", projectPath];
337
+ if (args.files?.length) {
338
+ cmdArgs.push("--files", ...args.files);
339
+ }
340
+ const result = await runCommand(node, [script, ...cmdArgs], contextDirectory(context), context.abort);
341
+ let parsed;
342
+ try {
343
+ parsed = JSON.parse(result.stdout);
344
+ } catch {
345
+ throw new Error(`arkts-check script returned unexpected output.
346
+ stdout: ${result.stdout}
347
+ stderr: ${result.stderr}`);
348
+ }
349
+ if (parsed.error) {
350
+ throw new Error(`arkts-check: ${parsed.error}`);
351
+ }
352
+ const { errors, summary } = parsed;
353
+ const lines = [
354
+ `ArkTS check complete: ${summary.errorCount} error(s), ${summary.warnCount} warning(s)`
355
+ ];
356
+ if (errors.length > 0) {
357
+ lines.push("");
358
+ for (const d of errors) {
359
+ const loc = `${d.file}:${d.line}:${d.column}`;
360
+ const rule = d.rule ? ` (${d.rule})` : "";
361
+ lines.push(`${d.severity.toUpperCase()} ${loc} ${d.message}${rule}`);
362
+ }
363
+ }
364
+ return {
365
+ title: parsed.success ? "ArkTS Check Passed" : `ArkTS Check Failed: ${summary.errorCount} error(s)`,
366
+ output: lines.join(`
367
+ `),
368
+ metadata: {
369
+ errorCount: summary.errorCount,
370
+ warnCount: summary.warnCount,
371
+ success: parsed.success
372
+ }
373
+ };
374
+ }
375
+ });
376
+ }
377
+
378
+ // src/tool/arkts-knowledge.ts
379
+ import { tool as tool2 } from "@opencode-ai/plugin";
380
+ var BASE_URL = "https://cn.devecostudio.huawei.com/codeGenie/bigSearch";
381
+ var MAX_RESULT_LENGTH = 5 * 1024;
382
+ var KNOWLEDGE_TOOL_ID = "arkts_knowledge_search";
383
+ function getDevEcoAccessToken() {
384
+ return process.env.DEVECO_OAUTH_TOKEN;
385
+ }
386
+ function createArktsKnowledgeTool() {
387
+ return tool2({
388
+ description: "Search the official ArkTS / ArkUI / HarmonyOS / OpenHarmony knowledge base. MUST call this tool before answering questions about .ets code, ArkUI decorators or lifecycle, state refresh issues, HarmonyOS SDK APIs, DevEco/hvigor build errors, @kit.* or @ohos.* APIs, or HarmonyOS documentation URLs. For code snippets or URLs, extract the key symbols, APIs, errors, and observed symptom as the question.",
389
+ args: {
390
+ question: tool2.schema.string().describe("A concise ArkTS/ArkUI/HarmonyOS/OpenHarmony question. Include key symbols, APIs, decorators, lifecycle names, build errors, symptoms, or URL-derived keywords.")
391
+ },
392
+ async execute(args, context) {
393
+ await context.ask({
394
+ permission: KNOWLEDGE_TOOL_ID,
395
+ patterns: [args.question],
396
+ always: ["*"],
397
+ metadata: {}
398
+ });
399
+ const accessToken = getDevEcoAccessToken();
400
+ if (!accessToken) {
401
+ throw new Error("Authorization fail: Please authorize to proceed.");
402
+ }
403
+ const response = await fetch(BASE_URL, {
404
+ method: "POST",
405
+ headers: {
406
+ Authorization: accessToken,
407
+ "Content-Type": "application/json"
408
+ },
409
+ body: JSON.stringify({ question: args.question })
410
+ });
411
+ const data = await response.json();
412
+ if ("code" in data && data.code === 200) {
413
+ if (data.body?.answer?.prompt) {
414
+ const mark = "【检索信息】:";
415
+ const prompt = data.body.answer.prompt;
416
+ const index = prompt.indexOf(mark);
417
+ if (index != -1) {
418
+ const result = prompt.substring(index + mark.length);
419
+ const truncatedResult = result.slice(0, MAX_RESULT_LENGTH);
420
+ return {
421
+ title: "HarmonyOS knowledge Search",
422
+ output: truncatedResult,
423
+ metadata: {}
424
+ };
425
+ }
426
+ }
427
+ return {
428
+ title: "HarmonyOS knowledge Search",
429
+ output: "No answer found for question",
430
+ metadata: {}
431
+ };
432
+ }
433
+ if ("error_code" in data) {
434
+ if (data.error_code === 4016) {
435
+ throw new Error("Authorization fail: Please authorize to proceed.");
436
+ }
437
+ throw new Error(`Service error: ${data.error_msg || "Unknown error"}`);
438
+ }
439
+ throw new Error("Unknown response format from knowledge service");
440
+ }
441
+ });
442
+ }
443
+
444
+ // src/tool/harmony-tools.ts
445
+ import { tool as tool3 } from "@opencode-ai/plugin";
446
+
447
+ // src/tool/harmony-bridge.ts
448
+ import { createRequire } from "node:module";
449
+ import { mkdirSync } from "node:fs";
450
+ import os3 from "node:os";
451
+ import path7 from "node:path";
452
+ var bridge;
453
+ var bound = "";
454
+ var gate = Promise.resolve();
455
+ function loadBridge() {
456
+ if (bridge)
457
+ return bridge;
458
+ try {
459
+ bridge = createRequire(import.meta.url)("@deveco-codegenie/mcp-bridge");
460
+ return bridge;
461
+ } catch (error) {
462
+ const detail = error instanceof Error ? error.message : String(error);
463
+ throw new Error(`@deveco-codegenie/mcp-bridge is not available: ${detail}`);
464
+ }
465
+ }
466
+ function bridgeLogDir() {
467
+ const base = process.env.XDG_DATA_HOME || path7.join(os3.homedir(), ".local", "share", "deveco");
468
+ const dir = path7.join(base, "log", "deveco-mcp");
469
+ mkdirSync(dir, { recursive: true });
470
+ return dir;
471
+ }
472
+ async function ensureInitialized(worktree) {
473
+ gate = gate.then(async () => {
474
+ if (bound === worktree)
475
+ return;
476
+ const home = findDevEcoHome();
477
+ if (!home)
478
+ throw new Error("DevEco Studio not found. Please set DEVECO_HOME to your DevEco installation directory.");
479
+ await loadBridge().init(bridgeLogDir(), worktree, home, process.env.UI_VERIFY_BASE_URL ?? null, process.env.UI_VERIFY_API_KEY ?? null, process.env.UI_VERIFY_MODEL_NAME ?? null);
480
+ bound = worktree;
481
+ });
482
+ await gate;
483
+ }
484
+ async function callHarmonyTool(worktree, toolName, args) {
485
+ await ensureInitialized(worktree);
486
+ const raw = await loadBridge().callTool(toolName, JSON.stringify(args));
487
+ return JSON.parse(raw);
488
+ }
489
+
490
+ // src/tool/harmony-tools.ts
491
+ function formatBridgeResult(name, result) {
492
+ if (typeof result === "string")
493
+ return result;
494
+ return JSON.stringify(result, null, 2);
495
+ }
496
+ function harmonyTool(name, definition) {
497
+ return tool3({
498
+ description: definition.description,
499
+ args: definition.args,
500
+ async execute(args, context) {
501
+ await context.ask({
502
+ permission: name,
503
+ patterns: [name],
504
+ always: [],
505
+ metadata: { tool: name }
506
+ });
507
+ const result = await callHarmonyTool(contextDirectory(context), name, args);
508
+ return {
509
+ title: name,
510
+ output: formatBridgeResult(name, result),
511
+ metadata: { tool: name }
512
+ };
513
+ }
514
+ });
515
+ }
516
+ function createHarmonyTools() {
517
+ return {
518
+ build_project: harmonyTool("build_project", {
519
+ description: "执行编译构建导出构建产物。支持构建整个 APP 或特定模块(HAP/HSP/HAR)。当需要重新构建或清理项目缓存时请将 clean 参数设为 true,这将在构建前执行清理clean。",
520
+ args: {
521
+ build_mode: tool3.schema.string().optional().nullable().describe("构建模式,值为 build-profile.json5 中 buildModeSet 定义的模式的 name 字段(如内置的 'debug'、'release',或用户自定义的模式名称)。如果不传,默认为 'debug'。"),
522
+ clean: tool3.schema.boolean().optional().nullable().describe("是否在构建前执行 clean 清理构建产物(类似 cargo clean或者mvn clean)。如果为 true,则会先执行清理然后再进行构建。"),
523
+ enable_inspector_source_jump: tool3.schema.boolean().optional().nullable().describe("支持hap包运行后,从获取到UI树json文件中精准对应到源码位置。"),
524
+ log_path: tool3.schema.string().optional().nullable().describe("构建日志保存路径:若指定,则将所有的构建日志保存到该路径下。日志文件使用后记得清理"),
525
+ module: tool3.schema.string().optional().nullable().describe("指定的模块及 Target(如 'entry@default',或省略 @target 构建该模块所有 target)。如果不传,则视为构建整个 APP。"),
526
+ product: tool3.schema.string().optional().nullable().describe("指定的 Product 名称(仅在构建整个 APP 时有效)")
527
+ }
528
+ }),
529
+ start_app: harmonyTool("start_app", {
530
+ description: "在模拟器/真机上运行应用(不主动构建)。设备列表包含已连接的真机、模拟器以及已安装但未运行的模拟器。选择未运行的模拟器时会自动启动。",
531
+ args: {
532
+ ability: tool3.schema.string().optional().nullable().describe("要启动的 Ability 名称(如 'EntryAbility')。如果不指定,默认使用 'EntryAbility'。"),
533
+ hvd: tool3.schema.string().optional().nullable().describe("目标设备的名称或 ID(支持真机或模拟器)。如果不提供,系统将列出所有可用设备供选择。"),
534
+ module: tool3.schema.string().optional().nullable().describe("要启动的模块名称(如 'entry')。如果不指定,默认使用 'entry'。"),
535
+ target: tool3.schema.string().optional().nullable().describe("构建目标(如 'default')。如果不指定,默认使用 'default'。")
536
+ }
537
+ }),
538
+ verify_ui: harmonyTool("verify_ui", {
539
+ description: "在提供自然语言描述的功能步骤之后,本工具可以在HarmonyOS设备上运行应用,执行UI操作,验证功能是否可以正确完成。",
540
+ args: {
541
+ bundleName: tool3.schema.string().optional().nullable().describe("待测试的应用包名,不填写时自动从项目中获取"),
542
+ device: tool3.schema.string().optional().nullable().describe("接受设备名称(支持子串匹配)或序列号(例如 127.0.0.1:5555);仅连接一台设备时自动选中,多设备环境下必须指定。"),
543
+ freshStart: tool3.schema.boolean().default(false).describe("是否在测试前重新启动应用"),
544
+ testPlan: tool3.schema.string().describe("自然语言描述的测试用例计划,包括每步的步骤以及预期结果")
545
+ }
546
+ }),
547
+ save_ui_screenshot: harmonyTool("save_ui_screenshot", {
548
+ description: "根据校验ID保存某次校验的每一步截图,返回截图文件名列表",
549
+ args: {
550
+ id: tool3.schema.string().describe("verify_ui 工具返回的校验任务 ID"),
551
+ dirname: tool3.schema.string().describe("截图保存目录,必须为绝对路径")
552
+ }
553
+ }),
554
+ get_ui_verification_log: harmonyTool("get_ui_verification_log", {
555
+ description: "根据校验ID获取对应的设备运行日志",
556
+ args: {
557
+ id: tool3.schema.string().describe("校验ID"),
558
+ maxLogSize: tool3.schema.number().int().optional().nullable().describe("可选的日志总字符数限制:默认为5000字符;传-1代表不限制字符数"),
559
+ searchKeywords: tool3.schema.string().optional().nullable().describe("可选的日志搜索关键词。若需要获取完整日志内容,请传空字符串。")
560
+ }
561
+ })
562
+ };
563
+ }
564
+
565
+ // src/tool/hdc-log.ts
566
+ import { tool as tool4 } from "@opencode-ai/plugin";
567
+ function pick(input, prefix, lines) {
568
+ const list = input.split(/\r?\n/).map((item) => item.trim()).filter(Boolean);
569
+ const filtered = prefix ? list.filter((item) => item.includes(prefix)) : list;
570
+ return filtered.slice(Math.max(0, filtered.length - lines));
571
+ }
572
+ function target(device) {
573
+ return device ? ["-t", device] : [];
574
+ }
575
+ async function runHdc(args, context) {
576
+ const home = findDevEcoHome();
577
+ if (!home)
578
+ throw new Error("DevEco Studio path not found. Set DEVECO_HOME and retry.");
579
+ const hdc = hdcPath(home);
580
+ const result = await runCommand(hdc, args, contextDirectory(context), context.abort);
581
+ if (result.code !== 0) {
582
+ throw new Error(`hdc ${args.join(" ")} failed (code=${result.code}): ${result.stderr || result.stdout}`);
583
+ }
584
+ return result.stdout;
585
+ }
586
+ function createHdcLogTool() {
587
+ return tool4({
588
+ description: "Collect, clear, or list HarmonyOS device logs via hdc shell hilog.",
589
+ args: {
590
+ action: tool4.schema.enum(["collect", "clear", "list_devices"]).describe("Action to perform"),
591
+ device_id: tool4.schema.string().optional().describe("Optional hdc target id"),
592
+ log_prefix: tool4.schema.string().default("[VCODER_DEBUG]").describe("Log prefix to filter"),
593
+ lines: tool4.schema.number().int().min(1).max(5000).default(2000).describe("Number of log lines to collect")
594
+ },
595
+ async execute(args, context) {
596
+ await context.ask({
597
+ permission: "hdc_log",
598
+ patterns: [args.action],
599
+ always: [],
600
+ metadata: { action: args.action, device: args.device_id }
601
+ });
602
+ if (args.action === "list_devices") {
603
+ const stdout2 = await runHdc(["list", "targets"], context);
604
+ const devices = stdout2.split(/\r?\n/).map((item) => item.trim()).filter((item) => item && !item.includes("[Empty]"));
605
+ return {
606
+ title: devices.length ? "Connected Devices" : "No Devices",
607
+ output: devices.length ? ["Connected devices:", ...devices.map((item, i) => `${i + 1}. ${item}`)].join(`
608
+ `) : "No connected devices detected.",
609
+ metadata: { deviceCount: devices.length }
610
+ };
611
+ }
612
+ if (args.action === "clear") {
613
+ await runHdc([...target(args.device_id), "shell", "hilog", "-r"], context);
614
+ return {
615
+ title: "Log Buffer Cleared",
616
+ output: ["Device log buffer cleared.", `device: ${args.device_id || "default"}`].join(`
617
+ `),
618
+ metadata: {}
619
+ };
620
+ }
621
+ const stdout = await runHdc([...target(args.device_id), "shell", "hilog", "-x"], context);
622
+ const logs = pick(stdout, args.log_prefix ?? "", args.lines ?? 2000);
623
+ if (!logs.length) {
624
+ return {
625
+ title: "No Matching Logs",
626
+ output: [
627
+ "No matching logs found.",
628
+ `device: ${args.device_id || "default"}`,
629
+ `prefix: ${args.log_prefix}`
630
+ ].join(`
631
+ `),
632
+ metadata: { lineCount: 0 }
633
+ };
634
+ }
635
+ return {
636
+ title: "Log Collection Successful",
637
+ output: [
638
+ "Log collection successful.",
639
+ `device: ${args.device_id || "default"}`,
640
+ `prefix: ${args.log_prefix}`,
641
+ `count: ${logs.length}`,
642
+ "",
643
+ "--- Log Content ---",
644
+ ...logs
645
+ ].join(`
646
+ `),
647
+ metadata: { lineCount: logs.length }
648
+ };
649
+ }
650
+ });
651
+ }
652
+
653
+ // src/tool/plan-write.ts
654
+ import { mkdir, writeFile } from "node:fs/promises";
655
+ import path8 from "node:path";
656
+ import { tool as tool5 } from "@opencode-ai/plugin";
657
+ function createPlanWriteTool() {
658
+ return tool5({
659
+ description: "Write the full plan file content, including YAML frontmatter and markdown body.",
660
+ args: {
661
+ content: tool5.schema.string().describe("The full plan file content, including YAML frontmatter and markdown body.")
662
+ },
663
+ async execute(args, context) {
664
+ await context.ask({
665
+ permission: "plan_write",
666
+ patterns: [".opencode/plans/*"],
667
+ always: [],
668
+ metadata: {}
669
+ });
670
+ const planDir = path8.join(context.directory, ".opencode", "plans");
671
+ const planPath = path8.join(planDir, `${context.sessionID}.md`);
672
+ await mkdir(planDir, { recursive: true });
673
+ await writeFile(planPath, args.content, "utf8");
674
+ return {
675
+ title: "Plan Written",
676
+ output: `Plan written to ${planPath}`,
677
+ metadata: { path: planPath }
678
+ };
679
+ }
680
+ });
681
+ }
682
+
683
+ // src/tool/switch-cwd.ts
684
+ import { realpathSync, statSync } from "node:fs";
685
+ import path9 from "node:path";
686
+ import { tool as tool6 } from "@opencode-ai/plugin";
687
+ function resolveTarget(projectPath, base) {
688
+ const trimmed = projectPath.trim();
689
+ if (!trimmed)
690
+ throw new Error("project_path must not be empty");
691
+ if (path9.isAbsolute(trimmed))
692
+ return path9.normalize(trimmed);
693
+ return path9.resolve(base, trimmed);
694
+ }
695
+ function createSwitchCwdTool() {
696
+ return tool6({
697
+ description: "Switch the active HarmonyOS project context before running build_project or start_app.",
698
+ args: {
699
+ project_path: tool6.schema.string().describe("Target project directory path. Relative path is resolved from the current workspace directory.")
700
+ },
701
+ async execute(args, context) {
702
+ const target2 = realpathSync(resolveTarget(args.project_path, context.directory));
703
+ const stat = statSync(target2);
704
+ if (!stat.isDirectory())
705
+ throw new Error(`Not a directory or not found: ${target2}`);
706
+ await context.ask({
707
+ permission: "switch_cwd",
708
+ patterns: [target2],
709
+ always: [],
710
+ metadata: { target: target2 }
711
+ });
712
+ setSessionCwd(context.sessionID, target2);
713
+ return {
714
+ title: "Switch project context",
715
+ output: `Session directory updated to ${target2}.`,
716
+ metadata: { project_path: target2 }
717
+ };
718
+ }
719
+ });
720
+ }
721
+
722
+ // src/tool/index.ts
723
+ function createTools(config) {
724
+ const tools = {
725
+ ...createHarmonyTools(),
726
+ arkts_check: createArktsCheckTool(),
727
+ hdc_log: createHdcLogTool(),
728
+ switch_cwd: createSwitchCwdTool(),
729
+ plan_write: createPlanWriteTool()
730
+ };
731
+ if (getDevEcoAccessToken()) {
732
+ tools.arkts_knowledge_search = createArktsKnowledgeTool();
733
+ }
734
+ return tools;
735
+ }
736
+ // src/create-plugin-module.ts
737
+ function createPluginModule() {
738
+ return {
739
+ id: "deveco-harness",
740
+ server: async (input) => {
741
+ const pluginConfig = loadDevecoAgentConfig(input.directory);
742
+ return {
743
+ tool: createTools(pluginConfig),
744
+ config: async (hostConfig) => {
745
+ applyAgentConfig(hostConfig, pluginConfig);
746
+ applySkillConfig(hostConfig, pluginConfig, input.directory);
747
+ }
748
+ };
749
+ }
750
+ };
751
+ }
752
+
753
+ // src/index.ts
754
+ var pluginModule = createPluginModule();
755
+ var src_default = pluginModule;
756
+ export {
757
+ src_default as default
758
+ };