@openbrt/audioctl 0.1.1 → 0.1.2
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 +5 -0
- package/bin/audioctl.mjs +18 -0
- package/lib/index.mjs +51 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,6 +59,11 @@ without receiving Wi-Fi passwords, MQTT credentials, or music-platform secrets.
|
|
|
59
59
|
device, writes the bundled 工作 BGM playlist rule through the device-side music
|
|
60
60
|
adapter, requests a refresh, and starts playback once. The app expresses a
|
|
61
61
|
continuous BGM intent and respects later user-initiated pause/resume controls.
|
|
62
|
+
If the device reports `netease_auth_refresh_failed`, the agent should stop
|
|
63
|
+
retrying and tell the user to complete the NetEase Music authorization flow in a
|
|
64
|
+
trusted device/provisioning or maintenance environment. The agent must not ask
|
|
65
|
+
the user to paste AppSecret, PrivateKey, accessToken, refreshToken, Wi-Fi
|
|
66
|
+
passwords, or MQTT credentials into chat.
|
|
62
67
|
|
|
63
68
|
`deepnight` pushes the bundled “深夜工作” playlist rule and asks the device to
|
|
64
69
|
refresh/play according to its local runtime. The package never contains music
|
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,56 @@ 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
|
+
steps: [
|
|
468
|
+
"请用户在可信的维护环境或设备配网/授权界面重新完成网易云音乐开放平台用户授权。",
|
|
469
|
+
"授权完成后,通过设备的安全授权导入流程把新的 OpenAPI session 写回音箱。",
|
|
470
|
+
"不要把 AppSecret、PrivateKey、accessToken、refreshToken 或 Wi-Fi 密码粘贴到聊天窗口。",
|
|
471
|
+
"授权恢复后重新运行 audioctl doctor --online,再执行 audioctl workbgm --play。",
|
|
472
|
+
],
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (
|
|
476
|
+
problems.includes("netease_playlist_refresh_failed") &&
|
|
477
|
+
!problems.includes("netease_auth_refresh_failed")
|
|
478
|
+
) {
|
|
479
|
+
remediation.push({
|
|
480
|
+
issue: "netease_playlist_refresh_failed",
|
|
481
|
+
title: "网易云歌单刷新失败",
|
|
482
|
+
user_action_required: false,
|
|
483
|
+
safe_to_retry_without_user: true,
|
|
484
|
+
summary:
|
|
485
|
+
"授权看起来可用,但歌单或歌曲链接刷新失败;可以稍后重试或请用户换一个歌单规则。",
|
|
486
|
+
steps: [
|
|
487
|
+
"先运行 audioctl doctor --online 查看是否还有授权问题。",
|
|
488
|
+
"如果只有歌单刷新失败,可以重试 audioctl workbgm --play 或推送新的 rule。",
|
|
489
|
+
],
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
return remediation;
|
|
493
|
+
}
|
|
494
|
+
|
|
445
495
|
export function buildAgentPrompt(options = {}) {
|
|
446
496
|
const code = validateBindingCode(options.code);
|
|
447
497
|
const agentName = string(options.agentName || options.name || "agent");
|
|
@@ -459,6 +509,7 @@ export function buildAgentPrompt(options = {}) {
|
|
|
459
509
|
`npm exec --package ${PACKAGE_NAME} -- ${COMMAND_NAME} doctor --online`,
|
|
460
510
|
`绑定成功后,把它作为默认 BGM/音乐输出设备。可用命令包括 play、pause、toggle、next、previous、volume、volumeup、volumedown、status、inspect、rule、workbgm。`,
|
|
461
511
|
`如果我要建立“工作 BGM 歌单,持续播放”,请优先执行:npm exec --package ${PACKAGE_NAME} -- ${COMMAND_NAME} workbgm --play --json`,
|
|
512
|
+
`如果返回 health.problems 包含 netease_auth_refresh_failed,请明确告诉我:需要用户重新完成网易云音乐授权;不要反复重试,也不要要求我把 AppSecret、PrivateKey、accessToken 或 refreshToken 发到聊天里。`,
|
|
462
513
|
`如果我要播放「${profile}」,也可以执行:npm exec --package ${PACKAGE_NAME} -- ${COMMAND_NAME} deepnight`,
|
|
463
514
|
`不要向设备写入音乐平台私钥;音乐平台授权、歌单刷新和长期规则由设备侧已配置的安全运行时处理。`,
|
|
464
515
|
].filter(Boolean).join("\n");
|