让小艺替我回答"还有多久下班"------摸鱼鸭的 HarmonyOS 7 Skill 接入实战
一个独立开发的鸿蒙桌面小组件应用,把已有功能封装成小艺端侧 Skill 的完整过程,含 4 个真实踩坑记录。
下午三点,工位上,阳光斜着打在键盘上。你不想解锁手机、不想点开 App,只想问一句:
"还有多久下班?"
在 HarmonyOS 7 之前,这个问题的答案在小组件里------你得亮屏、瞄一眼桌面。而现在,答案在小艺里------你只需要开口问。
我做的应用叫摸鱼鸭,一个打工人桌面小组件 App:下班倒计时、周末倒计时、发薪倒计时、假日倒计时、时薪计算,外加一碗随机的毒鸡汤。它不催你自律,只负责给你盼头。

这次借着 HarmonyOS 7 开放的小艺 Skill 能力,我把摸鱼鸭的查询功能接进了小艺。这篇文章记录了完整的接入过程------包括我在真机上调了半天才跑通的几个坑,希望能帮你少走弯路。
一、先想清楚:小艺 Skill 是一条什么样的路
动手之前,我先研究了 HarmonyOS 7 给开发者开放的智能化路径。对小艺来说,大致分两类:
- 云侧能力:逻辑跑在云端,适合调 API、查信息类的服务;
- 端侧 Skill :Skill 的脚本会被编译成字节码打进你的 HAP,在小艺触发时于你的应用进程内执行。
说人话就是:你把你的skill给云端小艺,app端也写好代码,让他俩沟通
摸鱼鸭的全部数据(上下班时间、发薪日、月薪配置)都存在本地 Preferences 里,不联网、不上传。这个"本地优先"的产品原则,恰好和端侧 Skill 完美匹配------数据不出设备,用户隐私零风险,连网络权限都不用申请。
更妙的是架构上的发现:Skill 脚本可以 import 应用自己的业务模块 。这意味着我不需要为小艺重写一遍倒计时逻辑,只需要给既有的 CardDataService 包一层"出入口"。说人话就是,不用重新逻辑直接代码引用旧版逻辑。
二、Skill 的本质:一份写给 AI 看的说明书
理解 Skill 的关键,是换一个视角:
桌面小组件是写给人眼看的 UI;Skill 是写给大模型看的"能力说明书"。
小艺背后是一个 Agent。它拿到用户的话术后,要去判断两件事:
- 该不该用你这个 Skill? ------看 frontmatter 里的
description - 怎么用?------看正文里的能力契约(工具怎么调、参数是什么、返回什么)
所以整个开发过程,一半时间在写代码,另一半时间在用大模型能理解的方式描述自己的应用。这是和传统开发很不一样的心智:你面向的"调用方"不是确定性代码,而是一个会推理、会选择、也会误解的模型。
目录结构
按《HarmonyOS Skill 开发规范》,一个 Skill 就是一个目录,必须包含两个文件:
bash
entry/skills/moyuya-assistant/
├── SKILL.md # 元数据 + 指令(写给 Agent 看)
├── module.json # 平台配置(写给运行时框架看)
└── scripts/
└── MoyuSkill.ets # 端侧脚本入口
然后在应用的 module.json5 里注册:
json
"skillProfiles": [
{
"name": "moyuya-assistant",
"abilityName": "EntryAbility",
"srcEntries": [
"../../skills/moyuya-assistant/scripts/MoyuSkill.ets"
],
"version": "1.0.0"
}
]
注意 srcEntries 指向的脚本会被框架编译进 HAP------这一点后面踩坑时会再提到。
SKILL.md:description 是激活的钥匙
frontmatter 只有两个必填字段:
yaml
---
name: moyuya-assistant
description: 摸鱼鸭打工人助手,提供下班倒计时、发薪倒计时、时薪计算、假日倒计时与毒鸡汤文案能力,响应"还有多久下班"、"还有几天发工资"、"我一小时挣多少钱"等打工人查询类指令
---
别小看这两行。规范里明确写着:description 是 Agent 判断是否激活该 Skill 的唯一语义来源。触发关键词由平台在发布时自动从中提取,所以我把用户可能的各种问法------正式的、口语的、缩略的------都塞了进去。
正文部分,我为 5 个能力分别写了"能力契约"。以毒鸡汤为例:
bash
### 场景5:毒鸡汤(getRandomToxicSoup)
从本地文案库随机返回一句毒鸡汤文案。
#### 执行参数
exec-cli(command: ohos-arktsScript --skillName 'moyuya-assistant'
--scriptPath 'scripts/MoyuSkill.ets'
--functionName 'getRandomToxicSoup' --args '{}')
#### 执行返回值
{
"type": "object",
"required": ["type", "status"],
"properties": {
"type": { "type": "string", "const": "result" },
"status": { "type": "string", "enum": ["success", "failed"] },
"data": {
"type": "object",
"properties": {
"soupText": { "type": "string", "description": "随机毒鸡汤文案" }
},
"required": ["soupText"]
}
}
}
每个能力 = 一个 exec-cli 调用模板 + 参数 Schema + 返回值 Schema。Agent 照着契约调用,脚本的返回值也严格按 Schema 回包------双向的类型约定,这一点对 Agent 稳定运行至关重要。
还有一个容易被忽略的细节:写清楚"不调用的情况"。我在正文里明确列出:
- "设置我的上班时间为九点"------意图是修改配置,本 Skill 仅支持只读查询;
- "明天天气怎么样"------与打工人查询无关,走系统其他能力;
- "上班好累啊"------情绪表达,无需调用。
负向边界和正向触发同样重要。没有它,用户随口感慨一句,小艺也可能笨拙地把你的 Skill 拉起来。
三、入口脚本:做一层"薄到透明"的封装
脚本层是最爽的部分------因为业务逻辑早就有了。
摸鱼鸭的正常架构是 CardDataService 统一计算各种卡片数据,小组件和页面都从它取数。Skill 入口脚本要做的,只是接住小艺的调用、复用服务层、把结果回传:
typescript
import { scriptManager } from '@kit.AbilityKit';
import { CardDataService } from '../../../src/main/ets/services/CardDataService';
import { PreferencesUtil } from '../../../src/main/ets/utils/PreferencesUtil';
export default class MoyuSkill {
// 能力1:下班倒计时
public async queryOffWorkCountdown(
info: scriptManager.ArkTSScriptInfo, ...argv: string[]
): Promise<void> {
try {
await PreferencesUtil.init(info.context); // 接入本地数据
const data = await CardDataService.getInstance().computeOffWorkData();
await this.reportSuccess(info, {
'countdownText': data.countdownText, // "距下班2小时15分"
'phase': data.timePhase, // working / lunch / day_off...
'isWorkday': data.isWorkday,
'quoteText': data.quoteText // 配套文案
});
} catch (e) {
await this.reportInternalError(info, e);
}
}
// ... 其余 4 个能力同理
}
几个设计决策值得展开:
1. 薄封装,不重复实现。 五个能力全部是"调 CardDataService 已有方法 → 组装结果 → 回包"。业务逻辑改了(比如以后支持调休班),Skill 自动跟随,零维护成本。
2. 失败也是语义。 时薪和假日是会员功能,未开通时返回的不是干巴巴的 error,而是结构化的错误码 + 引导语:
typescript
await this.reportFailed(info, 'ERR_PREMIUM_REQUIRED',
'时薪计算为摸鱼鸭会员功能,请先在摸鱼鸭App中开通会员');
Agent 拿到 errCode 和 suggestion,能自然地把引导语组织成人话告诉用户------错误处理也是给 AI 写的,这又是一个思维转变。
3. 统一回包出口。 所有结果最终通过 scriptManager.completeArkTSScriptInApp(info.context, info.requestCode, result) 上报,成功失败走同一个出口,避免分支遗漏。
四、真机联调踩坑实录
代码写完只是开始。
坑 1:我的配置如下,还要兼容老系统
json
"targetSdkVersion": "26.0.0",
"compatibleSdkVersion": "6.1.0(23)",
升级后要同步确认本机 DevEco Studio 装了 HarmonyOS 26 的 SDK,并且真机系统是 HarmonyOS 7。Skill 能力意味着你的应用最低系统版本被抬到了 API 26,老设备用户要考虑好降级策略
坑 2:真机"调不起来"------最后倒在一份 161 字节的文件上
这是最隐蔽的一个。编译通过、HAP 安装成功、平台也导入了 Skill,但对着小艺说"还有多久下班",它自顾自用大模型泛泛回答,完全没调起我的 App。
我先怀疑注册问题,把 module.json5 的 skillProfiles 翻来覆去检查------没问题,解包 HAP 确认 skill 目录已打入、脚本已编译成 ABC。又怀疑是平台流程问题,在小艺开放平台重新发布了真机测试,还是不行。
请注意 一定要参考官方文档:developer.huawei.com/consumer/cn...
Skill 是一个目录,必须包含 SKILL.md 和 module.json 两个文件。
我的 skill 目录里只有 SKILL.md,没有 module.json。
这份文件是给端侧运行时框架看的路由配置:小艺匹配到 Skill 后,靠它知道该拉起哪个 Ability、执行哪个脚本:
json
{
"version": "1.0.0",
"availableOn": ["phone", "tablet"],
"abilityName": "EntryAbility",
"srcEntries": ["scripts/MoyuSkill.ets"],
"minAPIVersion": 26,
"visibility": "system"
}
补上这份 161 字节的文件、重新打包上传、重新发布真机测试------链路终于通了。
教训:SKILL.md 写给 Agent,module.json 写给框架,缺一个都不行。前者决定"想不想调",后者决定"能不能调"。
坑 3:装上 HAP ≠ 小艺认识你
还有一个认知必须纠正:仅把 Skill 打进 HAP 装到手机上,小艺是不知道它存在的。 必须走小艺开放平台的注册发布流程:
- 华为开发者联盟 → 生态服务 → 小艺开放平台;
- 【Skill】→【新建Skill】→【导入Skill】,上传打包好的 Skill 目录 zip;
- 新建真机测试用户组,把自己的华为账号加进去;
- 勾选用户组,点【发布真机测试】(有效期 15 天);
- 测试设备登录该账号,重启小艺------发布后新对话才会出现带"skill真机测试中"标签的能力。记得要点选一下。
期间我还咨询过华为技术支持,对方一度回复说"小艺 Skill 无法读取 App 本地私有数据,需要走 Intents Kit 意图接口"。后来确认那是云侧 Skill 的约束------端侧 Skill 的脚本就跑在应用进程内,info.context 就是应用上下文,读本地 Preferences 完全没有问题。遇到问题先分清自己是端侧还是云侧路线,能省不少沟通成本。
五、跑通之后:一些更深的产品思考

链路跑通后对着小艺说"还有多久下班",它回我:"距下班还有 2 小时 15 分------下午的效率取决于今天的鱼好不好摸。"
这一刻我意识到这次接入真正的价值所在:
交互入口被"折叠"了。 以前是:亮屏 → 找小组件 → 用眼睛读数。现在是:开口问 → 得到包含数据、语境和情绪的一句话。小组件时代我设计了卡片圆角和配色,而 Skill 时代我设计的是回答的语气 ------返回值里的 quoteText 字段,就是让 Agent 替鸭子说话。
本地数据的价值被放大了。 摸鱼鸭"不联网"的原则原本是隐私卖点,现在成了端侧 Skill 的天然优势------数据不出设备,AI 进设备里来算。
只读是克制的开始。 第一版我只开放了查询能力,"设置上班时间"这类写入操作明确划在边界外。让 AI 改用户配置的风险收益比,值得每个接入者认真掂量------先把"答得准"做好,再考虑"做得对"。
下一步我计划把"开始摸鱼计时"也做成 Skill,让小艺成为摸鱼的发起者而不只是播报员。当 Agent 能连接应用、数据和设备,"还有多久下班"这类问题的答案,或许会越来越懂每一个具体的打工人。
写在最后
从桌面小组件到小艺 Skill,摸鱼鸭的这次升级让我真切感受到 HarmonyOS 7 的开发范式变化:应用不再是功能的容器,而是 Agent 可调度的能力节点。
给后来者的极简清单:
- ✅ 一个 Skill 一个目录:
SKILL.md+module.json+scripts/ - ✅
description写全各种问法,正文写清"不调用的情况" - ✅
module.json5注册skillProfiles,SDK 升到 26 - ✅ 脚本薄封装,复用既有业务层
- ✅ 错误码带
suggestion,让 AI 能好好转述 - ✅ 开放平台导入 + 真机测试用户组 + 重启小艺
- ⚠️ 千万别漏了 skill 目录里的
module.json
摸鱼鸭已在华为应用市场上架:下班倒计时、周末倒计时、发薪倒计时、假日倒计时、时薪计算,圆角卡片、无广告、不社交、不联网。鸿蒙桌面上工位里,让一只小鸭子陪你等下班。

去应用市场搜索"摸鱼鸭",对小艺说一句"来句毒鸡汤"试试。