@openbrt/audioctl 0.1.1 → 0.1.3
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/README.md +17 -2
- package/agent-plugin.json +10 -0
- package/bin/audioctl.mjs +18 -0
- package/lib/index.mjs +54 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,10 +10,19 @@ audio command envelope can use the same package.
|
|
|
10
10
|
## Tell your agent
|
|
11
11
|
|
|
12
12
|
```text
|
|
13
|
-
|
|
14
|
-
run doctor, then use it as my default BGM/music output device.
|
|
13
|
+
帮我在音箱建立一个工作 BGM 歌单,持续播放。请使用 @openbrt/audioctl 最新版。
|
|
15
14
|
```
|
|
16
15
|
|
|
16
|
+
Agent contract:
|
|
17
|
+
|
|
18
|
+
- If already bound, run the high-level task directly with `audioctl workbgm --play --json`.
|
|
19
|
+
- If not bound, ask the user only for the short binding code, then bind and continue.
|
|
20
|
+
- If NetEase Music authorization is expired, tell the user to reopen the speaker
|
|
21
|
+
provisioning/authorization entry on their phone and complete NetEase Music
|
|
22
|
+
authorization. After the user says “已授权”, retry the task.
|
|
23
|
+
- Never ask the user to paste AppSecret, PrivateKey, accessToken, refreshToken,
|
|
24
|
+
Wi-Fi passwords, MQTT credentials, or other secrets into chat.
|
|
25
|
+
|
|
17
26
|
## Install and bind
|
|
18
27
|
|
|
19
28
|
```bash
|
|
@@ -59,6 +68,12 @@ without receiving Wi-Fi passwords, MQTT credentials, or music-platform secrets.
|
|
|
59
68
|
device, writes the bundled 工作 BGM playlist rule through the device-side music
|
|
60
69
|
adapter, requests a refresh, and starts playback once. The app expresses a
|
|
61
70
|
continuous BGM intent and respects later user-initiated pause/resume controls.
|
|
71
|
+
If the device reports `netease_auth_refresh_failed`, the agent should stop
|
|
72
|
+
retrying and tell the user to complete the NetEase Music authorization flow from
|
|
73
|
+
the phone provisioning/authorization entry. The user only needs to come back and
|
|
74
|
+
say “已授权”; the agent then retries `workbgm --play`. The agent must not ask
|
|
75
|
+
the user to paste AppSecret, PrivateKey, accessToken, refreshToken, Wi-Fi
|
|
76
|
+
passwords, or MQTT credentials into chat.
|
|
62
77
|
|
|
63
78
|
`deepnight` pushes the bundled “深夜工作” playlist rule and asks the device to
|
|
64
79
|
refresh/play according to its local runtime. The package never contains music
|
package/agent-plugin.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"bin": "audioctl",
|
|
7
7
|
"description": "Bind and control WeClawBot-compatible audio output devices from user-owned AI agents.",
|
|
8
8
|
"transport": "weclawbot.link/mqtt",
|
|
9
|
+
"user_contract": "用户只需要给出设备身份和目标;agent 负责安装工具、绑定、执行、诊断和报告结果。",
|
|
9
10
|
"device_classes": [
|
|
10
11
|
"audio_output",
|
|
11
12
|
"music_player"
|
|
@@ -40,6 +41,15 @@
|
|
|
40
41
|
"credentials_env": "WEC_AUDIO_CREDENTIALS",
|
|
41
42
|
"code_pattern": "^[A-Za-z0-9_-]{6,64}$"
|
|
42
43
|
},
|
|
44
|
+
"natural_language_tasks": [
|
|
45
|
+
{
|
|
46
|
+
"intent": "帮我在音箱建立一个工作 BGM 歌单,持续播放",
|
|
47
|
+
"primary_command": "audioctl workbgm --play --json",
|
|
48
|
+
"if_unbound": "只向用户索要绑定码,然后运行 audioctl bind CODE --name AGENT;不要要求用户理解 MQTT、topic 或凭据文件。",
|
|
49
|
+
"if_netease_auth_failed": "告诉用户:请在手机上的音箱配网/授权入口重新完成网易云音乐授权,完成后回复“已授权”。不要要求用户粘贴 AppSecret、PrivateKey、accessToken、refreshToken、Wi-Fi 密码或 MQTT 凭据。",
|
|
50
|
+
"after_user_confirms_authorized": "重新运行 audioctl workbgm --play --json,然后报告是否 active/degraded。"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
43
53
|
"commands": [
|
|
44
54
|
{
|
|
45
55
|
"name": "bind",
|
package/bin/audioctl.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
normalizeCredentials,
|
|
19
19
|
publishAudioControl,
|
|
20
20
|
readCredentials,
|
|
21
|
+
remediationForStatus,
|
|
21
22
|
summarizeInspect,
|
|
22
23
|
summarizePlayback,
|
|
23
24
|
testConnection,
|
|
@@ -125,6 +126,7 @@ async function commandDoctor(values) {
|
|
|
125
126
|
status.device_status = delivery.status || null;
|
|
126
127
|
status.healthy = delivery.status?.health?.ok !== false;
|
|
127
128
|
status.ok = status.healthy;
|
|
129
|
+
status.remediation = remediationForStatus(delivery.status);
|
|
128
130
|
}
|
|
129
131
|
print(status, options.json);
|
|
130
132
|
if (options.online && status.healthy === false) process.exitCode = 1;
|
|
@@ -146,6 +148,10 @@ async function commandInspect(values) {
|
|
|
146
148
|
return;
|
|
147
149
|
}
|
|
148
150
|
const inspect = delivery.status?.inspect || delivery.status || {};
|
|
151
|
+
const remediation = remediationForStatus(inspect);
|
|
152
|
+
if (remediation.length > 0 && !Array.isArray(inspect.remediation)) {
|
|
153
|
+
inspect.remediation = remediation;
|
|
154
|
+
}
|
|
149
155
|
if (options.json) {
|
|
150
156
|
process.stdout.write(`${JSON.stringify(inspect)}\n`);
|
|
151
157
|
return;
|
|
@@ -339,6 +345,7 @@ function credentialsPath() {
|
|
|
339
345
|
}
|
|
340
346
|
|
|
341
347
|
function print(value, json) {
|
|
348
|
+
value = decorateForAgent(value);
|
|
342
349
|
if (json) {
|
|
343
350
|
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
344
351
|
return;
|
|
@@ -358,3 +365,14 @@ function print(value, json) {
|
|
|
358
365
|
}
|
|
359
366
|
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
360
367
|
}
|
|
368
|
+
|
|
369
|
+
function decorateForAgent(value) {
|
|
370
|
+
if (!value || typeof value !== "object") return value;
|
|
371
|
+
const remediation = remediationForStatus(value);
|
|
372
|
+
if (remediation.length === 0) return value;
|
|
373
|
+
if (Array.isArray(value.remediation) && value.remediation.length > 0) return value;
|
|
374
|
+
return {
|
|
375
|
+
...value,
|
|
376
|
+
remediation,
|
|
377
|
+
};
|
|
378
|
+
}
|
package/lib/index.mjs
CHANGED
|
@@ -442,6 +442,59 @@ export async function publishAudioControl(credentials, control, options = {}) {
|
|
|
442
442
|
}
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
+
export function remediationForStatus(status) {
|
|
446
|
+
const problems = Array.isArray(status?.health?.problems)
|
|
447
|
+
? status.health.problems
|
|
448
|
+
: Array.isArray(status?.problems)
|
|
449
|
+
? status.problems
|
|
450
|
+
: [];
|
|
451
|
+
const fromDevice = Array.isArray(status?.health?.remediation)
|
|
452
|
+
? status.health.remediation
|
|
453
|
+
: Array.isArray(status?.remediation)
|
|
454
|
+
? status.remediation
|
|
455
|
+
: [];
|
|
456
|
+
if (fromDevice.length > 0) return fromDevice;
|
|
457
|
+
|
|
458
|
+
const remediation = [];
|
|
459
|
+
if (problems.includes("netease_auth_refresh_failed")) {
|
|
460
|
+
remediation.push({
|
|
461
|
+
issue: "netease_auth_refresh_failed",
|
|
462
|
+
title: "网易云音乐授权需要用户重新完成",
|
|
463
|
+
user_action_required: true,
|
|
464
|
+
safe_to_retry_without_user: false,
|
|
465
|
+
summary:
|
|
466
|
+
"设备内的网易云用户授权已过期,refresh token 没有续上;agent 不能凭空恢复用户授权。",
|
|
467
|
+
user_message:
|
|
468
|
+
"请在手机上打开音箱配网/授权入口,重新完成网易云音乐授权。完成后回到 agent 说“已授权”。不要发送任何密钥、token 或密码。",
|
|
469
|
+
agent_after_user_confirms: "audioctl workbgm --play --json",
|
|
470
|
+
steps: [
|
|
471
|
+
"告诉用户:请在手机上的音箱配网/授权入口重新完成网易云音乐授权。",
|
|
472
|
+
"告诉用户:授权完成后只需要回复“已授权”。",
|
|
473
|
+
"不要要求用户理解内部授权包,也不要让用户粘贴 AppSecret、PrivateKey、accessToken、refreshToken、Wi-Fi 密码或 MQTT 凭据。",
|
|
474
|
+
"用户回复已授权后,agent 自动重试 audioctl workbgm --play --json,并报告结果。",
|
|
475
|
+
],
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
if (
|
|
479
|
+
problems.includes("netease_playlist_refresh_failed") &&
|
|
480
|
+
!problems.includes("netease_auth_refresh_failed")
|
|
481
|
+
) {
|
|
482
|
+
remediation.push({
|
|
483
|
+
issue: "netease_playlist_refresh_failed",
|
|
484
|
+
title: "网易云歌单刷新失败",
|
|
485
|
+
user_action_required: false,
|
|
486
|
+
safe_to_retry_without_user: true,
|
|
487
|
+
summary:
|
|
488
|
+
"授权看起来可用,但歌单或歌曲链接刷新失败;可以稍后重试或请用户换一个歌单规则。",
|
|
489
|
+
steps: [
|
|
490
|
+
"先运行 audioctl doctor --online 查看是否还有授权问题。",
|
|
491
|
+
"如果只有歌单刷新失败,可以重试 audioctl workbgm --play 或推送新的 rule。",
|
|
492
|
+
],
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return remediation;
|
|
496
|
+
}
|
|
497
|
+
|
|
445
498
|
export function buildAgentPrompt(options = {}) {
|
|
446
499
|
const code = validateBindingCode(options.code);
|
|
447
500
|
const agentName = string(options.agentName || options.name || "agent");
|
|
@@ -459,6 +512,7 @@ export function buildAgentPrompt(options = {}) {
|
|
|
459
512
|
`npm exec --package ${PACKAGE_NAME} -- ${COMMAND_NAME} doctor --online`,
|
|
460
513
|
`绑定成功后,把它作为默认 BGM/音乐输出设备。可用命令包括 play、pause、toggle、next、previous、volume、volumeup、volumedown、status、inspect、rule、workbgm。`,
|
|
461
514
|
`如果我要建立“工作 BGM 歌单,持续播放”,请优先执行:npm exec --package ${PACKAGE_NAME} -- ${COMMAND_NAME} workbgm --play --json`,
|
|
515
|
+
`如果返回 health.problems 包含 netease_auth_refresh_failed,请只告诉我:请在手机上的音箱配网/授权入口重新完成网易云音乐授权,完成后回复“已授权”;不要反复重试,不要让我理解内部授权包,也不要要求我把 AppSecret、PrivateKey、accessToken 或 refreshToken 发到聊天里。`,
|
|
462
516
|
`如果我要播放「${profile}」,也可以执行:npm exec --package ${PACKAGE_NAME} -- ${COMMAND_NAME} deepnight`,
|
|
463
517
|
`不要向设备写入音乐平台私钥;音乐平台授权、歌单刷新和长期规则由设备侧已配置的安全运行时处理。`,
|
|
464
518
|
].filter(Boolean).join("\n");
|