@yoooclaw/cli 0.0.3 → 0.0.4
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 -2
- package/dist/bin.cjs +1088 -281
- package/dist/bin.cjs.map +19 -12
- package/dist/index.cjs +1088 -281
- package/dist/index.cjs.map +19 -12
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -2944,8 +2944,8 @@ async function writeTextAtomic(filePath, content, options) {
|
|
|
2944
2944
|
const mkdirOptions = { recursive: true };
|
|
2945
2945
|
if (typeof options?.ensureDirMode === "number")
|
|
2946
2946
|
mkdirOptions.mode = options.ensureDirMode;
|
|
2947
|
-
await import_promises3.default.mkdir(
|
|
2948
|
-
const parentDir =
|
|
2947
|
+
await import_promises3.default.mkdir(import_node_path9.default.dirname(filePath), mkdirOptions);
|
|
2948
|
+
const parentDir = import_node_path9.default.dirname(filePath);
|
|
2949
2949
|
const tmp = `${filePath}.${import_node_crypto5.randomUUID()}.tmp`;
|
|
2950
2950
|
try {
|
|
2951
2951
|
const tmpHandle = await import_promises3.default.open(tmp, "w", mode);
|
|
@@ -2996,9 +2996,9 @@ function createAsyncLock() {
|
|
|
2996
2996
|
}
|
|
2997
2997
|
};
|
|
2998
2998
|
}
|
|
2999
|
-
var
|
|
2999
|
+
var import_node_path9, import_promises3, import_node_crypto5;
|
|
3000
3000
|
var init_json_files_DMrq2IfK = __esm(() => {
|
|
3001
|
-
|
|
3001
|
+
import_node_path9 = __toESM(require("node:path"));
|
|
3002
3002
|
import_promises3 = __toESM(require("node:fs/promises"));
|
|
3003
3003
|
import_node_crypto5 = require("node:crypto");
|
|
3004
3004
|
});
|
|
@@ -3014,7 +3014,7 @@ function normalize(value) {
|
|
|
3014
3014
|
}
|
|
3015
3015
|
function resolveEffectiveHomeDir(env = process.env, homedir3 = import_node_os4.default.homedir) {
|
|
3016
3016
|
const raw = resolveRawHomeDir(env, homedir3);
|
|
3017
|
-
return raw ?
|
|
3017
|
+
return raw ? import_node_path10.default.resolve(raw) : undefined;
|
|
3018
3018
|
}
|
|
3019
3019
|
function resolveRawHomeDir(env, homedir3) {
|
|
3020
3020
|
const explicitHome = normalize(env.OPENCLAW_HOME);
|
|
@@ -3046,7 +3046,7 @@ function normalizeSafe(homedir3) {
|
|
|
3046
3046
|
}
|
|
3047
3047
|
}
|
|
3048
3048
|
function resolveRequiredHomeDir(env = process.env, homedir3 = import_node_os4.default.homedir) {
|
|
3049
|
-
return resolveEffectiveHomeDir(env, homedir3) ??
|
|
3049
|
+
return resolveEffectiveHomeDir(env, homedir3) ?? import_node_path10.default.resolve(process.cwd());
|
|
3050
3050
|
}
|
|
3051
3051
|
function expandHomePrefix(input, opts) {
|
|
3052
3052
|
if (!input.startsWith("~"))
|
|
@@ -3066,13 +3066,13 @@ function resolveHomeRelativePath(input, opts) {
|
|
|
3066
3066
|
env: opts?.env,
|
|
3067
3067
|
homedir: opts?.homedir
|
|
3068
3068
|
});
|
|
3069
|
-
return
|
|
3069
|
+
return import_node_path10.default.resolve(expanded);
|
|
3070
3070
|
}
|
|
3071
|
-
return
|
|
3071
|
+
return import_node_path10.default.resolve(trimmed);
|
|
3072
3072
|
}
|
|
3073
|
-
var
|
|
3073
|
+
var import_node_path10, import_node_os4;
|
|
3074
3074
|
var init_home_dir_BnP38vVl = __esm(() => {
|
|
3075
|
-
|
|
3075
|
+
import_node_path10 = __toESM(require("node:path"));
|
|
3076
3076
|
import_node_os4 = __toESM(require("node:os"));
|
|
3077
3077
|
});
|
|
3078
3078
|
|
|
@@ -3087,10 +3087,10 @@ function envHomedir(env) {
|
|
|
3087
3087
|
return () => resolveRequiredHomeDir(env, import_node_os5.default.homedir);
|
|
3088
3088
|
}
|
|
3089
3089
|
function legacyStateDirs(homedir3 = resolveDefaultHomeDir) {
|
|
3090
|
-
return LEGACY_STATE_DIRNAMES.map((dir) =>
|
|
3090
|
+
return LEGACY_STATE_DIRNAMES.map((dir) => import_node_path11.default.join(homedir3(), dir));
|
|
3091
3091
|
}
|
|
3092
3092
|
function newStateDir(homedir3 = resolveDefaultHomeDir) {
|
|
3093
|
-
return
|
|
3093
|
+
return import_node_path11.default.join(homedir3(), NEW_STATE_DIRNAME);
|
|
3094
3094
|
}
|
|
3095
3095
|
function resolveStateDir2(env = process.env, homedir3 = envHomedir(env)) {
|
|
3096
3096
|
const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir3);
|
|
@@ -3101,11 +3101,11 @@ function resolveStateDir2(env = process.env, homedir3 = envHomedir(env)) {
|
|
|
3101
3101
|
if (env.OPENCLAW_TEST_FAST === "1")
|
|
3102
3102
|
return newDir;
|
|
3103
3103
|
const legacyDirs = legacyStateDirs(effectiveHomedir);
|
|
3104
|
-
if (
|
|
3104
|
+
if (import_node_fs15.default.existsSync(newDir))
|
|
3105
3105
|
return newDir;
|
|
3106
3106
|
const existingLegacy = legacyDirs.find((dir) => {
|
|
3107
3107
|
try {
|
|
3108
|
-
return
|
|
3108
|
+
return import_node_fs15.default.existsSync(dir);
|
|
3109
3109
|
} catch {
|
|
3110
3110
|
return false;
|
|
3111
3111
|
}
|
|
@@ -3124,14 +3124,14 @@ function resolveCanonicalConfigPath(env = process.env, stateDir = resolveStateDi
|
|
|
3124
3124
|
const override = env.OPENCLAW_CONFIG_PATH?.trim();
|
|
3125
3125
|
if (override)
|
|
3126
3126
|
return resolveUserPath(override, env, envHomedir(env));
|
|
3127
|
-
return
|
|
3127
|
+
return import_node_path11.default.join(stateDir, CONFIG_FILENAME);
|
|
3128
3128
|
}
|
|
3129
3129
|
function resolveConfigPathCandidate(env = process.env, homedir3 = envHomedir(env)) {
|
|
3130
3130
|
if (env.OPENCLAW_TEST_FAST === "1")
|
|
3131
3131
|
return resolveCanonicalConfigPath(env, resolveStateDir2(env, homedir3));
|
|
3132
3132
|
const existing = resolveDefaultConfigCandidates(env, homedir3).find((candidate) => {
|
|
3133
3133
|
try {
|
|
3134
|
-
return
|
|
3134
|
+
return import_node_fs15.default.existsSync(candidate);
|
|
3135
3135
|
} catch {
|
|
3136
3136
|
return false;
|
|
3137
3137
|
}
|
|
@@ -3149,21 +3149,21 @@ function resolveDefaultConfigCandidates(env = process.env, homedir3 = envHomedir
|
|
|
3149
3149
|
const openclawStateDir = env.OPENCLAW_STATE_DIR?.trim();
|
|
3150
3150
|
if (openclawStateDir) {
|
|
3151
3151
|
const resolved = resolveUserPath(openclawStateDir, env, effectiveHomedir);
|
|
3152
|
-
candidates.push(
|
|
3153
|
-
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) =>
|
|
3152
|
+
candidates.push(import_node_path11.default.join(resolved, CONFIG_FILENAME));
|
|
3153
|
+
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => import_node_path11.default.join(resolved, name)));
|
|
3154
3154
|
}
|
|
3155
3155
|
const defaultDirs = [newStateDir(effectiveHomedir), ...legacyStateDirs(effectiveHomedir)];
|
|
3156
3156
|
for (const dir of defaultDirs) {
|
|
3157
|
-
candidates.push(
|
|
3158
|
-
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) =>
|
|
3157
|
+
candidates.push(import_node_path11.default.join(dir, CONFIG_FILENAME));
|
|
3158
|
+
candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => import_node_path11.default.join(dir, name)));
|
|
3159
3159
|
}
|
|
3160
3160
|
return candidates;
|
|
3161
3161
|
}
|
|
3162
|
-
var
|
|
3162
|
+
var import_node_fs15, import_node_path11, import_node_os5, isNixMode, LEGACY_STATE_DIRNAMES, NEW_STATE_DIRNAME = ".openclaw", CONFIG_FILENAME = "openclaw.json", LEGACY_CONFIG_FILENAMES, STATE_DIR, CONFIG_PATH;
|
|
3163
3163
|
var init_paths_Y4UT24Of = __esm(() => {
|
|
3164
3164
|
init_home_dir_BnP38vVl();
|
|
3165
|
-
|
|
3166
|
-
|
|
3165
|
+
import_node_fs15 = __toESM(require("node:fs"));
|
|
3166
|
+
import_node_path11 = __toESM(require("node:path"));
|
|
3167
3167
|
import_node_os5 = __toESM(require("node:os"));
|
|
3168
3168
|
isNixMode = resolveIsNixMode();
|
|
3169
3169
|
LEGACY_STATE_DIRNAMES = [".clawdbot", ".moldbot"];
|
|
@@ -3215,11 +3215,11 @@ function resolveMissingRequestedScope(params) {
|
|
|
3215
3215
|
}
|
|
3216
3216
|
function resolvePairingPaths(baseDir, subdir) {
|
|
3217
3217
|
const root = baseDir ?? resolveStateDir2();
|
|
3218
|
-
const dir =
|
|
3218
|
+
const dir = import_node_path12.default.join(root, subdir);
|
|
3219
3219
|
return {
|
|
3220
3220
|
dir,
|
|
3221
|
-
pendingPath:
|
|
3222
|
-
pairedPath:
|
|
3221
|
+
pendingPath: import_node_path12.default.join(dir, "pending.json"),
|
|
3222
|
+
pairedPath: import_node_path12.default.join(dir, "paired.json")
|
|
3223
3223
|
};
|
|
3224
3224
|
}
|
|
3225
3225
|
function pruneExpiredPending(pendingById, nowMs, ttlMs) {
|
|
@@ -3241,10 +3241,10 @@ function verifyPairingToken(provided, expected) {
|
|
|
3241
3241
|
return false;
|
|
3242
3242
|
return safeEqualSecret(provided, expected);
|
|
3243
3243
|
}
|
|
3244
|
-
var
|
|
3244
|
+
var import_node_path12, import_node_crypto6, OPERATOR_ROLE = "operator", OPERATOR_ADMIN_SCOPE = "operator.admin", OPERATOR_READ_SCOPE = "operator.read", OPERATOR_WRITE_SCOPE = "operator.write", OPERATOR_SCOPE_PREFIX = "operator.";
|
|
3245
3245
|
var init_pairing_token_C5g4QivV = __esm(() => {
|
|
3246
3246
|
init_paths_Y4UT24Of();
|
|
3247
|
-
|
|
3247
|
+
import_node_path12 = __toESM(require("node:path"));
|
|
3248
3248
|
import_node_crypto6 = require("node:crypto");
|
|
3249
3249
|
});
|
|
3250
3250
|
|
|
@@ -3454,7 +3454,7 @@ function sameDeviceBootstrapProfile(left, right) {
|
|
|
3454
3454
|
return left.roles.length === right.roles.length && left.scopes.length === right.scopes.length && left.roles.every((value, index) => value === right.roles[index]) && left.scopes.every((value, index) => value === right.scopes[index]);
|
|
3455
3455
|
}
|
|
3456
3456
|
function resolveBootstrapPath(baseDir) {
|
|
3457
|
-
return
|
|
3457
|
+
return import_node_path13.default.join(resolvePairingPaths(baseDir, "devices").dir, "bootstrap.json");
|
|
3458
3458
|
}
|
|
3459
3459
|
function resolvePersistedBootstrapProfile(record) {
|
|
3460
3460
|
return normalizeDeviceBootstrapProfile(record.profile ?? record);
|
|
@@ -3536,12 +3536,12 @@ async function revokeDeviceBootstrapToken(params) {
|
|
|
3536
3536
|
return { removed: true };
|
|
3537
3537
|
});
|
|
3538
3538
|
}
|
|
3539
|
-
var
|
|
3539
|
+
var import_node_path13, PAIRING_SETUP_BOOTSTRAP_PROFILE, DEVICE_BOOTSTRAP_TOKEN_TTL_MS, withLock2;
|
|
3540
3540
|
var init_device_bootstrap_DKsEcfsd = __esm(() => {
|
|
3541
3541
|
init_json_files_DMrq2IfK();
|
|
3542
3542
|
init_pairing_token_C5g4QivV();
|
|
3543
3543
|
init_device_auth_BRUxebMD();
|
|
3544
|
-
|
|
3544
|
+
import_node_path13 = __toESM(require("node:path"));
|
|
3545
3545
|
PAIRING_SETUP_BOOTSTRAP_PROFILE = {
|
|
3546
3546
|
roles: ["node"],
|
|
3547
3547
|
scopes: []
|
|
@@ -3806,16 +3806,17 @@ var COMMAND_TREE = [
|
|
|
3806
3806
|
subcommands: [
|
|
3807
3807
|
{
|
|
3808
3808
|
name: "send",
|
|
3809
|
-
summary: "发送灯效指令到硬件(--segments / --preset
|
|
3809
|
+
summary: "发送灯效指令到硬件(--segments / --preset / --rule 三选一)",
|
|
3810
3810
|
options: [
|
|
3811
|
-
{ flags: "--segments <json>", summary: "灯效参数 JSON" },
|
|
3812
|
-
{ flags: "--preset <name>", summary: "
|
|
3813
|
-
{ flags: "--
|
|
3814
|
-
{ flags: "--repeat
|
|
3811
|
+
{ flags: "--segments <json>", summary: "灯效参数 JSON(原始段)" },
|
|
3812
|
+
{ flags: "--preset <name>", summary: "内置预设 id(如 red-steady / red-strobe-3)" },
|
|
3813
|
+
{ flags: "--rule <name>", summary: "已保存的 lightrule 名" },
|
|
3814
|
+
{ flags: "--repeat", summary: "无限循环播放(覆盖来源默认值)" },
|
|
3815
|
+
{ flags: "--repeat-times <n>", summary: "整条组合重复次数(0=无限,覆盖来源默认值)" }
|
|
3815
3816
|
]
|
|
3816
3817
|
}
|
|
3817
3818
|
],
|
|
3818
|
-
shortcuts: [{ name: "+blink", summary: "
|
|
3819
|
+
shortcuts: [{ name: "+blink", summary: "灯效连通性测试(red-strobe-3)" }]
|
|
3819
3820
|
},
|
|
3820
3821
|
{
|
|
3821
3822
|
name: "lightrule",
|
|
@@ -3959,11 +3960,13 @@ var COMMAND_TREE = [
|
|
|
3959
3960
|
summary: "Agent 技能管理:把随包发布的 SKILL.md 装到 agent 可发现目录 \uD83D\uDFE2",
|
|
3960
3961
|
subcommands: [
|
|
3961
3962
|
{ name: "list", summary: "列出随 CLI 发布的内置 Skill 及其触发说明" },
|
|
3963
|
+
{ name: "targets", summary: "列出支持的 Agent skills 目录与自动探测结果" },
|
|
3962
3964
|
{
|
|
3963
3965
|
name: "install",
|
|
3964
|
-
summary: "把内置 Skill 安装到 agent skills
|
|
3966
|
+
summary: "把内置 Skill 安装到 agent skills 目录(默认自动探测,软链)",
|
|
3965
3967
|
options: [
|
|
3966
|
-
{ flags: "--
|
|
3968
|
+
{ flags: "--agent <agent>", summary: "安装目标 agent:auto|claude|codex|custom", default: "auto" },
|
|
3969
|
+
{ flags: "--target <dir>", summary: "安装目标目录;传入后优先于自动探测" },
|
|
3967
3970
|
{ flags: "--copy", summary: "复制而非软链(升级后不随包自动更新)" },
|
|
3968
3971
|
{ flags: "--force", summary: "目标已存在同名 Skill 时覆盖" }
|
|
3969
3972
|
]
|
|
@@ -4191,7 +4194,7 @@ function buildContext(flags) {
|
|
|
4191
4194
|
var import_node_fs3 = require("node:fs");
|
|
4192
4195
|
function readBuildInjectedVersion() {
|
|
4193
4196
|
if (false) {}
|
|
4194
|
-
const version = "0.0.
|
|
4197
|
+
const version = "0.0.4".trim();
|
|
4195
4198
|
return version || undefined;
|
|
4196
4199
|
}
|
|
4197
4200
|
function readVersionFromPackageJson() {
|
|
@@ -5010,7 +5013,7 @@ async function authCheck(ctx) {
|
|
|
5010
5013
|
}
|
|
5011
5014
|
|
|
5012
5015
|
// src/notification/query.ts
|
|
5013
|
-
var
|
|
5016
|
+
var import_node_fs16 = require("node:fs");
|
|
5014
5017
|
|
|
5015
5018
|
// ../phone-notifications/src/cli/helpers.ts
|
|
5016
5019
|
var import_node_fs8 = require("node:fs");
|
|
@@ -6735,9 +6738,655 @@ function registerLightRulesGateway(api, registry, logger, rememberBroadcast) {
|
|
|
6735
6738
|
}
|
|
6736
6739
|
});
|
|
6737
6740
|
}
|
|
6738
|
-
// ../phone-notifications/src/
|
|
6741
|
+
// ../phone-notifications/src/light/sender.ts
|
|
6742
|
+
var import_node_crypto4 = require("node:crypto");
|
|
6743
|
+
|
|
6744
|
+
// ../phone-notifications/src/light/protocol.ts
|
|
6745
|
+
var MAX_LIGHT_SEGMENTS = 12;
|
|
6746
|
+
var PROTOCOL_DIGITS = [
|
|
6747
|
+
"",
|
|
6748
|
+
"",
|
|
6749
|
+
"",
|
|
6750
|
+
"",
|
|
6751
|
+
"",
|
|
6752
|
+
"",
|
|
6753
|
+
"",
|
|
6754
|
+
"",
|
|
6755
|
+
"",
|
|
6756
|
+
"",
|
|
6757
|
+
"",
|
|
6758
|
+
""
|
|
6759
|
+
];
|
|
6760
|
+
var LED_SEPARATOR_ONCE = "";
|
|
6761
|
+
var LED_SEPARATOR_LOOP = "";
|
|
6762
|
+
var DURATION_STEPS_S = [0.5, 1, 2, 3, 5, 6, 8, 16, 24, 32, 48];
|
|
6763
|
+
var INTERVAL_STEPS_MS = [50, 100, 200, 300, 500, 600, 800, 1600, 2400, 3200, 4800];
|
|
6764
|
+
var BREATH_STEPS_MS = [1040, 1560, 2080, 2600, 3100, 4160];
|
|
6765
|
+
var BRIGHTNESS_STEPS = [32, 64, 96, 128, 192, 255];
|
|
6766
|
+
var COLOR_STEPS = [0, 32, 64, 128, 192, 255];
|
|
6767
|
+
var BACKGROUND_BRIGHTNESS_STEPS = [0, 32, 64, 96, 128, 192, 255];
|
|
6768
|
+
var MULTI_CHANNEL_COLOR_COEFFICIENTS = { r: 1, g: 0.25, b: 0.25 };
|
|
6769
|
+
var PURE_WHITE_COLOR_COEFFICIENTS = { r: 1, g: 0.35, b: 0.35 };
|
|
6770
|
+
var MODE_TO_INDEX = {
|
|
6771
|
+
wave: 0,
|
|
6772
|
+
breath: 1,
|
|
6773
|
+
strobe: 2,
|
|
6774
|
+
steady: 3,
|
|
6775
|
+
color_flow: 4,
|
|
6776
|
+
pixel_frame: 5
|
|
6777
|
+
};
|
|
6778
|
+
function buildLightEffectApnsBody(segments, repeatInput, visibleTextOverride) {
|
|
6779
|
+
assertSegmentCount(segments);
|
|
6780
|
+
assertSegmentsValid(segments);
|
|
6781
|
+
const repeatTimes = normalizeRepeatTimes(repeatInput);
|
|
6782
|
+
assertAncsRepeatTimes(repeatTimes);
|
|
6783
|
+
const visibleText = resolveVisibleText(visibleTextOverride, segments);
|
|
6784
|
+
const separator = repeatTimes === 0 ? LED_SEPARATOR_LOOP : LED_SEPARATOR_ONCE;
|
|
6785
|
+
const payload = segments.map((segment) => encodeSegment(segment)).join("");
|
|
6786
|
+
return `${visibleText}${separator}${payload}`;
|
|
6787
|
+
}
|
|
6788
|
+
function resolveVisibleText(override, segments) {
|
|
6789
|
+
const trimmed = override?.trim();
|
|
6790
|
+
if (trimmed)
|
|
6791
|
+
return trimmed;
|
|
6792
|
+
return summarizeSegments(segments);
|
|
6793
|
+
}
|
|
6794
|
+
function assertSegmentCount(segments) {
|
|
6795
|
+
if (segments.length < 1 || segments.length > MAX_LIGHT_SEGMENTS) {
|
|
6796
|
+
throw new Error(`light_control supports 1-${MAX_LIGHT_SEGMENTS} segments`);
|
|
6797
|
+
}
|
|
6798
|
+
}
|
|
6799
|
+
function summarizeSegments(segments) {
|
|
6800
|
+
const modeDesc = segments.map((segment) => segment.mode).join("+");
|
|
6801
|
+
return `Effect: ${modeDesc} (${segments.length} segment${segments.length > 1 ? "s" : ""})`;
|
|
6802
|
+
}
|
|
6803
|
+
function assertSegmentsValid(segments) {
|
|
6804
|
+
const validation = validateSegments(segments);
|
|
6805
|
+
if (!validation.valid) {
|
|
6806
|
+
throw new Error(validation.errors.map((error) => `${error.field}: ${error.message}`).join("; "));
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
function encodeSegment(segment) {
|
|
6810
|
+
const common = [
|
|
6811
|
+
MODE_TO_INDEX[segment.mode],
|
|
6812
|
+
quantizeDuration(segment.duration_s)
|
|
6813
|
+
];
|
|
6814
|
+
let values;
|
|
6815
|
+
switch (segment.mode) {
|
|
6816
|
+
case "wave":
|
|
6817
|
+
case "color_flow":
|
|
6818
|
+
const color = normalizeProtocolColor(segment.color);
|
|
6819
|
+
const background = normalizeProtocolColor(segment.background);
|
|
6820
|
+
values = [
|
|
6821
|
+
...common,
|
|
6822
|
+
quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
|
|
6823
|
+
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
6824
|
+
quantize(color.r, COLOR_STEPS),
|
|
6825
|
+
quantize(color.g, COLOR_STEPS),
|
|
6826
|
+
quantize(color.b, COLOR_STEPS),
|
|
6827
|
+
segment.direction === "rtl" ? 1 : 0,
|
|
6828
|
+
quantizeWindow(segment.window ?? 2),
|
|
6829
|
+
quantize(background.r, COLOR_STEPS),
|
|
6830
|
+
quantize(background.g, COLOR_STEPS),
|
|
6831
|
+
quantize(background.b, COLOR_STEPS),
|
|
6832
|
+
quantize(segment.background?.brightness ?? 0, BACKGROUND_BRIGHTNESS_STEPS)
|
|
6833
|
+
];
|
|
6834
|
+
break;
|
|
6835
|
+
case "breath":
|
|
6836
|
+
const breathColor = normalizeProtocolColor(segment.color);
|
|
6837
|
+
values = [
|
|
6838
|
+
...common,
|
|
6839
|
+
quantizeBreathRiseFall(segment.breath_timing?.rise_ms),
|
|
6840
|
+
quantizeBreathHoldOff(segment.breath_timing?.hold_ms),
|
|
6841
|
+
quantizeBreathRiseFall(segment.breath_timing?.fall_ms),
|
|
6842
|
+
quantizeBreathHoldOff(segment.breath_timing?.off_ms),
|
|
6843
|
+
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
6844
|
+
quantize(breathColor.r, COLOR_STEPS),
|
|
6845
|
+
quantize(breathColor.g, COLOR_STEPS),
|
|
6846
|
+
quantize(breathColor.b, COLOR_STEPS)
|
|
6847
|
+
];
|
|
6848
|
+
break;
|
|
6849
|
+
case "strobe":
|
|
6850
|
+
const strobeColor = normalizeProtocolColor(segment.color);
|
|
6851
|
+
values = [
|
|
6852
|
+
...common,
|
|
6853
|
+
quantize(segment.interval_ms ?? 200, INTERVAL_STEPS_MS),
|
|
6854
|
+
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
6855
|
+
quantize(strobeColor.r, COLOR_STEPS),
|
|
6856
|
+
quantize(strobeColor.g, COLOR_STEPS),
|
|
6857
|
+
quantize(strobeColor.b, COLOR_STEPS)
|
|
6858
|
+
];
|
|
6859
|
+
break;
|
|
6860
|
+
case "steady":
|
|
6861
|
+
const steadyColor = normalizeProtocolColor(segment.color);
|
|
6862
|
+
values = [
|
|
6863
|
+
...common,
|
|
6864
|
+
quantizeBrightnessValue(segment.brightness ?? 0),
|
|
6865
|
+
quantize(steadyColor.r, COLOR_STEPS),
|
|
6866
|
+
quantize(steadyColor.g, COLOR_STEPS),
|
|
6867
|
+
quantize(steadyColor.b, COLOR_STEPS)
|
|
6868
|
+
];
|
|
6869
|
+
break;
|
|
6870
|
+
case "pixel_frame":
|
|
6871
|
+
values = encodePixelFrameValues(common, segment);
|
|
6872
|
+
break;
|
|
6873
|
+
}
|
|
6874
|
+
return values.map((value) => PROTOCOL_DIGITS[value]).join("");
|
|
6875
|
+
}
|
|
6876
|
+
function encodePixelFrameValues(common, segment) {
|
|
6877
|
+
const pixels = segment.pixels ?? [];
|
|
6878
|
+
return [
|
|
6879
|
+
...common,
|
|
6880
|
+
pixels.length - 1,
|
|
6881
|
+
...pixels.flatMap((pixel) => {
|
|
6882
|
+
const color = normalizeProtocolColor(pixel.color);
|
|
6883
|
+
return [
|
|
6884
|
+
pixel.index,
|
|
6885
|
+
quantize(color.r, COLOR_STEPS),
|
|
6886
|
+
quantize(color.g, COLOR_STEPS),
|
|
6887
|
+
quantize(color.b, COLOR_STEPS),
|
|
6888
|
+
quantizeBrightnessValue(pixel.brightness)
|
|
6889
|
+
];
|
|
6890
|
+
})
|
|
6891
|
+
];
|
|
6892
|
+
}
|
|
6893
|
+
function normalizeProtocolColor(color) {
|
|
6894
|
+
const normalized = {
|
|
6895
|
+
r: color?.r ?? 0,
|
|
6896
|
+
g: color?.g ?? 0,
|
|
6897
|
+
b: color?.b ?? 0
|
|
6898
|
+
};
|
|
6899
|
+
if (isPureWhiteProtocolColor(normalized)) {
|
|
6900
|
+
return applyProtocolColorCoefficients(normalized, PURE_WHITE_COLOR_COEFFICIENTS);
|
|
6901
|
+
}
|
|
6902
|
+
if (countActiveProtocolColorChannels(normalized) <= 1) {
|
|
6903
|
+
return normalized;
|
|
6904
|
+
}
|
|
6905
|
+
return applyProtocolColorCoefficients(normalized, MULTI_CHANNEL_COLOR_COEFFICIENTS);
|
|
6906
|
+
}
|
|
6907
|
+
function countActiveProtocolColorChannels(color) {
|
|
6908
|
+
return Number(color.r > 0) + Number(color.g > 0) + Number(color.b > 0);
|
|
6909
|
+
}
|
|
6910
|
+
function isPureWhiteProtocolColor(color) {
|
|
6911
|
+
return color.r === 255 && color.g === 255 && color.b === 255;
|
|
6912
|
+
}
|
|
6913
|
+
function applyProtocolColorCoefficients(color, coefficients) {
|
|
6914
|
+
return {
|
|
6915
|
+
r: scaleProtocolColorChannel(color.r, coefficients.r),
|
|
6916
|
+
g: scaleProtocolColorChannel(color.g, coefficients.g),
|
|
6917
|
+
b: scaleProtocolColorChannel(color.b, coefficients.b)
|
|
6918
|
+
};
|
|
6919
|
+
}
|
|
6920
|
+
function scaleProtocolColorChannel(value, coefficient) {
|
|
6921
|
+
return Math.max(0, Math.min(255, Math.round(value * coefficient)));
|
|
6922
|
+
}
|
|
6923
|
+
function quantize(value, steps) {
|
|
6924
|
+
let bestIndex = 0;
|
|
6925
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
6926
|
+
for (const [index, step] of steps.entries()) {
|
|
6927
|
+
const distance = Math.abs(value - step);
|
|
6928
|
+
if (distance < bestDistance) {
|
|
6929
|
+
bestIndex = index;
|
|
6930
|
+
bestDistance = distance;
|
|
6931
|
+
}
|
|
6932
|
+
}
|
|
6933
|
+
return bestIndex;
|
|
6934
|
+
}
|
|
6935
|
+
function quantizeDuration(duration_s) {
|
|
6936
|
+
if (duration_s === 0)
|
|
6937
|
+
return 11;
|
|
6938
|
+
return quantize(duration_s, DURATION_STEPS_S);
|
|
6939
|
+
}
|
|
6940
|
+
function quantizeBrightnessValue(brightness) {
|
|
6941
|
+
if (brightness === 0)
|
|
6942
|
+
return 11;
|
|
6943
|
+
return quantize(brightness, BRIGHTNESS_STEPS);
|
|
6944
|
+
}
|
|
6945
|
+
function quantizeBreathRiseFall(value) {
|
|
6946
|
+
return quantize(value ?? 1040, BREATH_STEPS_MS);
|
|
6947
|
+
}
|
|
6948
|
+
function quantizeBreathHoldOff(value) {
|
|
6949
|
+
if (value === 0) {
|
|
6950
|
+
return 5;
|
|
6951
|
+
}
|
|
6952
|
+
return quantize(value ?? 1040, BREATH_STEPS_MS.slice(0, 5));
|
|
6953
|
+
}
|
|
6954
|
+
function quantizeWindow(value) {
|
|
6955
|
+
return value - 1;
|
|
6956
|
+
}
|
|
6957
|
+
|
|
6958
|
+
// ../phone-notifications/src/env.ts
|
|
6959
|
+
var import_node_fs13 = require("node:fs");
|
|
6960
|
+
|
|
6961
|
+
// ../phone-notifications/src/auth/credentials.ts
|
|
6962
|
+
var import_node_fs12 = require("node:fs");
|
|
6963
|
+
|
|
6964
|
+
// ../phone-notifications/src/profile/paths.ts
|
|
6739
6965
|
var import_node_fs11 = require("node:fs");
|
|
6740
6966
|
var import_node_path7 = require("node:path");
|
|
6967
|
+
var DEFAULT_JVSCLAW_STATE_DIR = "/home/admin/.openclaw";
|
|
6968
|
+
function trimToUndefined(value) {
|
|
6969
|
+
if (typeof value !== "string") {
|
|
6970
|
+
return;
|
|
6971
|
+
}
|
|
6972
|
+
const trimmed = value.trim();
|
|
6973
|
+
return trimmed || undefined;
|
|
6974
|
+
}
|
|
6975
|
+
function homeDir() {
|
|
6976
|
+
return process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
6977
|
+
}
|
|
6978
|
+
function expandUserPath(value) {
|
|
6979
|
+
if (!value) {
|
|
6980
|
+
return;
|
|
6981
|
+
}
|
|
6982
|
+
if (value === "~") {
|
|
6983
|
+
return homeDir();
|
|
6984
|
+
}
|
|
6985
|
+
if (value.startsWith("~/")) {
|
|
6986
|
+
return import_node_path7.join(homeDir(), value.slice(2));
|
|
6987
|
+
}
|
|
6988
|
+
return value;
|
|
6989
|
+
}
|
|
6990
|
+
function candidateMetaPaths() {
|
|
6991
|
+
const home = homeDir();
|
|
6992
|
+
return [
|
|
6993
|
+
import_node_path7.join(home, ".qclaw", "qclaw.json"),
|
|
6994
|
+
import_node_path7.join(home, ".qclow", "qclaw.json")
|
|
6995
|
+
];
|
|
6996
|
+
}
|
|
6997
|
+
function loadQClawMeta() {
|
|
6998
|
+
for (const metaPath of candidateMetaPaths()) {
|
|
6999
|
+
if (!import_node_fs11.existsSync(metaPath)) {
|
|
7000
|
+
continue;
|
|
7001
|
+
}
|
|
7002
|
+
try {
|
|
7003
|
+
const parsed = JSON.parse(import_node_fs11.readFileSync(metaPath, "utf-8"));
|
|
7004
|
+
return {
|
|
7005
|
+
stateDir: expandUserPath(trimToUndefined(parsed?.stateDir)),
|
|
7006
|
+
configPath: expandUserPath(trimToUndefined(parsed?.configPath))
|
|
7007
|
+
};
|
|
7008
|
+
} catch {}
|
|
7009
|
+
}
|
|
7010
|
+
return;
|
|
7011
|
+
}
|
|
7012
|
+
function resolveStateDirFromEnv() {
|
|
7013
|
+
return expandUserPath(trimToUndefined(process.env.JVSCLAW_STATE_DIR) ?? trimToUndefined(process.env.JVSCLAW_HOME) ?? trimToUndefined(process.env.OPENCLAW_STATE_DIR) ?? trimToUndefined(process.env.QCLAW_STATE_DIR) ?? trimToUndefined(process.env.OPENCLAW_ROOT) ?? trimToUndefined(process.env.OPENCLAW_HOME) ?? trimToUndefined(process.env.QCLAW_HOME));
|
|
7014
|
+
}
|
|
7015
|
+
function hasJvsclawProfileEnv() {
|
|
7016
|
+
return trimToUndefined(process.env.CLAW_PROFILE)?.toLowerCase() === "jvsclaw";
|
|
7017
|
+
}
|
|
7018
|
+
function hasPersistedJvsclawProfile() {
|
|
7019
|
+
try {
|
|
7020
|
+
const content = import_node_fs11.readFileSync(import_node_path7.join(DEFAULT_JVSCLAW_STATE_DIR, ".env"), "utf-8");
|
|
7021
|
+
return content.split(`
|
|
7022
|
+
`).some((line) => line.trim().toLowerCase() === "claw_profile=jvsclaw");
|
|
7023
|
+
} catch {
|
|
7024
|
+
return false;
|
|
7025
|
+
}
|
|
7026
|
+
}
|
|
7027
|
+
function hasOpenClawMarkers() {
|
|
7028
|
+
const baseDir = import_node_path7.join(homeDir(), ".openclaw");
|
|
7029
|
+
return [
|
|
7030
|
+
import_node_path7.join(baseDir, "openclaw.json"),
|
|
7031
|
+
import_node_path7.join(baseDir, "credentials.json"),
|
|
7032
|
+
import_node_path7.join(baseDir, "extensions")
|
|
7033
|
+
].some((candidate) => import_node_fs11.existsSync(candidate));
|
|
7034
|
+
}
|
|
7035
|
+
function resolveStateDir() {
|
|
7036
|
+
const envDir = resolveStateDirFromEnv();
|
|
7037
|
+
if (envDir) {
|
|
7038
|
+
return envDir;
|
|
7039
|
+
}
|
|
7040
|
+
if (hasJvsclawProfileEnv() || hasPersistedJvsclawProfile()) {
|
|
7041
|
+
return DEFAULT_JVSCLAW_STATE_DIR;
|
|
7042
|
+
}
|
|
7043
|
+
if (hasOpenClawMarkers()) {
|
|
7044
|
+
return import_node_path7.join(homeDir(), ".openclaw");
|
|
7045
|
+
}
|
|
7046
|
+
const meta = loadQClawMeta();
|
|
7047
|
+
if (meta?.stateDir) {
|
|
7048
|
+
return meta.stateDir;
|
|
7049
|
+
}
|
|
7050
|
+
if (meta?.configPath) {
|
|
7051
|
+
return import_node_path7.dirname(meta.configPath);
|
|
7052
|
+
}
|
|
7053
|
+
return import_node_path7.join(homeDir(), ".openclaw");
|
|
7054
|
+
}
|
|
7055
|
+
function resolveStateFile(filename) {
|
|
7056
|
+
return import_node_path7.join(resolveStateDir(), filename);
|
|
7057
|
+
}
|
|
7058
|
+
|
|
7059
|
+
// ../phone-notifications/src/profile/detect.ts
|
|
7060
|
+
var VALID_CLAW_KINDS = new Set([
|
|
7061
|
+
"openclaw",
|
|
7062
|
+
"arkclaw",
|
|
7063
|
+
"jvsclaw"
|
|
7064
|
+
]);
|
|
7065
|
+
// ../phone-notifications/src/auth/credentials.ts
|
|
7066
|
+
function credentialsPath() {
|
|
7067
|
+
return resolveStateFile("credentials.json");
|
|
7068
|
+
}
|
|
7069
|
+
function readCredentials() {
|
|
7070
|
+
const path = credentialsPath();
|
|
7071
|
+
if (!import_node_fs12.existsSync(path))
|
|
7072
|
+
return {};
|
|
7073
|
+
try {
|
|
7074
|
+
return JSON.parse(import_node_fs12.readFileSync(path, "utf-8"));
|
|
7075
|
+
} catch {
|
|
7076
|
+
return {};
|
|
7077
|
+
}
|
|
7078
|
+
}
|
|
7079
|
+
|
|
7080
|
+
// ../phone-notifications/src/env.ts
|
|
7081
|
+
function getEnvHost(env) {
|
|
7082
|
+
switch (env) {
|
|
7083
|
+
case "development":
|
|
7084
|
+
return "openclaw-service-dev.yoooclaw.com";
|
|
7085
|
+
case "test":
|
|
7086
|
+
return "openclaw-service-test.yoooclaw.com";
|
|
7087
|
+
case "production":
|
|
7088
|
+
return "openclaw-service.yoooclaw.com";
|
|
7089
|
+
}
|
|
7090
|
+
}
|
|
7091
|
+
function buildEnvUrls(host) {
|
|
7092
|
+
const https = `https://${host}`;
|
|
7093
|
+
const wss = `wss://${host}`;
|
|
7094
|
+
return {
|
|
7095
|
+
lightApiUrl: `${https}/api/message/tob/sendMessage`,
|
|
7096
|
+
relayTunnelUrl: `${wss}/message/messages/ws/plugin`,
|
|
7097
|
+
appNameMapUrl: `${https}/api/application-config/app-package/config-all`,
|
|
7098
|
+
modelProxyLongRecordingSubmitTaskUrl: `${https}/api/model-proxy/long-recording/submit-task`,
|
|
7099
|
+
modelProxyLongRecordingQueryTaskResultBaseUrl: `${https}/api/model-proxy/long-recording/query-task-result`,
|
|
7100
|
+
accountFileDeleteUrl: `${https}/api/account/file/delete`,
|
|
7101
|
+
gatewayConnectStatusUrl: `${https}/api/message/messageBridge/plugin/gateway-connect-status`,
|
|
7102
|
+
clawManagerInstanceReadyUrl: `${https}/claw-manager/internal/claw-manager/instance/ready`
|
|
7103
|
+
};
|
|
7104
|
+
}
|
|
7105
|
+
var VALID_ENVS = new Set([
|
|
7106
|
+
"development",
|
|
7107
|
+
"test",
|
|
7108
|
+
"production"
|
|
7109
|
+
]);
|
|
7110
|
+
function readDotEnv() {
|
|
7111
|
+
const path = resolveStateFile(".env");
|
|
7112
|
+
if (!import_node_fs13.existsSync(path))
|
|
7113
|
+
return {};
|
|
7114
|
+
return Object.fromEntries(import_node_fs13.readFileSync(path, "utf-8").split(`
|
|
7115
|
+
`).flatMap((line) => {
|
|
7116
|
+
const eq = line.indexOf("=");
|
|
7117
|
+
if (eq < 1)
|
|
7118
|
+
return [];
|
|
7119
|
+
return [[line.slice(0, eq).trim(), line.slice(eq + 1).trim()]];
|
|
7120
|
+
}));
|
|
7121
|
+
}
|
|
7122
|
+
function readPersistedEnvName() {
|
|
7123
|
+
const fromDotEnv = readDotEnv()["PHONE_NOTIFICATIONS_ENV"]?.trim();
|
|
7124
|
+
if (fromDotEnv && VALID_ENVS.has(fromDotEnv))
|
|
7125
|
+
return fromDotEnv;
|
|
7126
|
+
return;
|
|
7127
|
+
}
|
|
7128
|
+
function loadEnvName() {
|
|
7129
|
+
const fromEnvVar = process.env.PHONE_NOTIFICATIONS_ENV?.trim();
|
|
7130
|
+
if (fromEnvVar && VALID_ENVS.has(fromEnvVar))
|
|
7131
|
+
return fromEnvVar;
|
|
7132
|
+
const fromDotEnv = readPersistedEnvName();
|
|
7133
|
+
if (fromDotEnv)
|
|
7134
|
+
return fromDotEnv;
|
|
7135
|
+
const { env } = readCredentials();
|
|
7136
|
+
if (env && VALID_ENVS.has(env))
|
|
7137
|
+
return env;
|
|
7138
|
+
return "production";
|
|
7139
|
+
}
|
|
7140
|
+
function getEnvUrls(env) {
|
|
7141
|
+
return buildEnvUrls(getEnvHost(env ?? loadEnvName()));
|
|
7142
|
+
}
|
|
7143
|
+
|
|
7144
|
+
// ../phone-notifications/src/light/sender.ts
|
|
7145
|
+
async function sendLightEffect(apiKey, segments, logger, repeatInput, reason, title) {
|
|
7146
|
+
const apiUrl = getEnvUrls().lightApiUrl;
|
|
7147
|
+
const appKey = process.env.LIGHT_APP_KEY;
|
|
7148
|
+
const templateId = process.env.LIGHT_TEMPLATE_ID;
|
|
7149
|
+
const resolvedTitle = resolveLightTitle(title, reason, segments);
|
|
7150
|
+
logger?.info(`Light sender: apiUrl=${apiUrl ?? "UNSET"}, appKey=${appKey ? appKey.substring(0, 8) + "…" : "UNSET"}, templateId=${templateId ?? "UNSET"}, apiKey=${apiKey ? apiKey.substring(0, 20) + "…" : "EMPTY"}, title=${resolvedTitle}, reason=${reason ?? ""}, segments=${JSON.stringify(segments)}`);
|
|
7151
|
+
if (!apiUrl || !appKey || !templateId) {
|
|
7152
|
+
return {
|
|
7153
|
+
ok: false,
|
|
7154
|
+
error: "灯效 API 未配置,请设置环境变量 LIGHT_API_URL / LIGHT_APP_KEY / LIGHT_TEMPLATE_ID"
|
|
7155
|
+
};
|
|
7156
|
+
}
|
|
7157
|
+
let bizContent;
|
|
7158
|
+
try {
|
|
7159
|
+
bizContent = buildLightEffectApnsBody(segments, repeatInput, reason);
|
|
7160
|
+
} catch (error) {
|
|
7161
|
+
return { ok: false, error: error?.message ?? String(error) };
|
|
7162
|
+
}
|
|
7163
|
+
const bizUniqueId = import_node_crypto4.randomUUID();
|
|
7164
|
+
const requestBody = {
|
|
7165
|
+
appKey,
|
|
7166
|
+
bizMap: { noticeType: "APP_NOTIFICATION_IMPORTANT", title: resolvedTitle, reason },
|
|
7167
|
+
bizUniqueId,
|
|
7168
|
+
paramsMap: { bizContent },
|
|
7169
|
+
pushType: "SPECIFY_PUSH",
|
|
7170
|
+
templateId
|
|
7171
|
+
};
|
|
7172
|
+
logger?.info(`Light sender: POST ${apiUrl}, bizUniqueId=${bizUniqueId}, body=${JSON.stringify(requestBody).substring(0, 500)}`);
|
|
7173
|
+
const res = await fetch(apiUrl, {
|
|
7174
|
+
method: "POST",
|
|
7175
|
+
headers: {
|
|
7176
|
+
"Content-Type": "application/json",
|
|
7177
|
+
"X-Api-Key-Id": apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey
|
|
7178
|
+
},
|
|
7179
|
+
body: JSON.stringify(requestBody)
|
|
7180
|
+
});
|
|
7181
|
+
const resBody = await res.text();
|
|
7182
|
+
if (!res.ok) {
|
|
7183
|
+
logger?.warn(`Light sender: FAILED ${res.status}, url=${apiUrl}, resBody=${resBody.substring(0, 500)}`);
|
|
7184
|
+
return { ok: false, status: res.status, error: resBody };
|
|
7185
|
+
}
|
|
7186
|
+
logger?.info(`Light sender: OK bizUniqueId=${bizUniqueId}, resBody=${resBody.substring(0, 200)}`);
|
|
7187
|
+
return { ok: true, bizUniqueId, response: JSON.parse(resBody) };
|
|
7188
|
+
}
|
|
7189
|
+
function resolveLightTitle(title, reason, segments) {
|
|
7190
|
+
const trimmedTitle = title?.trim();
|
|
7191
|
+
if (trimmedTitle)
|
|
7192
|
+
return trimmedTitle;
|
|
7193
|
+
const trimmedReason = reason?.trim();
|
|
7194
|
+
if (trimmedReason)
|
|
7195
|
+
return trimmedReason;
|
|
7196
|
+
const modeDesc = segments.map((segment) => segment.mode).join("+");
|
|
7197
|
+
return `Effect: ${modeDesc || "custom"}`;
|
|
7198
|
+
}
|
|
7199
|
+
// ../phone-notifications/src/light/presets-v1.json
|
|
7200
|
+
var presets_v1_default = {
|
|
7201
|
+
version: "v1",
|
|
7202
|
+
presets: [
|
|
7203
|
+
{
|
|
7204
|
+
presetId: "red-steady",
|
|
7205
|
+
displayName: "红色常亮",
|
|
7206
|
+
description: "红灯持续亮起 5 秒",
|
|
7207
|
+
repeat_times: 1,
|
|
7208
|
+
segments: [
|
|
7209
|
+
{
|
|
7210
|
+
mode: "steady",
|
|
7211
|
+
duration_s: 5,
|
|
7212
|
+
brightness: 192,
|
|
7213
|
+
color: {
|
|
7214
|
+
r: 255,
|
|
7215
|
+
g: 0,
|
|
7216
|
+
b: 0
|
|
7217
|
+
}
|
|
7218
|
+
}
|
|
7219
|
+
],
|
|
7220
|
+
base64: "RWZmZWN0OiBzdGVhZHkgKDEgc2VnbWVudCnCmsKDwoTChMKRwoDCgA=="
|
|
7221
|
+
},
|
|
7222
|
+
{
|
|
7223
|
+
presetId: "red-breath",
|
|
7224
|
+
displayName: "红色呼吸",
|
|
7225
|
+
description: "红色缓慢呼吸约两轮",
|
|
7226
|
+
repeat_times: 1,
|
|
7227
|
+
segments: [
|
|
7228
|
+
{
|
|
7229
|
+
mode: "breath",
|
|
7230
|
+
duration_s: 8,
|
|
7231
|
+
brightness: 192,
|
|
7232
|
+
color: {
|
|
7233
|
+
r: 255,
|
|
7234
|
+
g: 0,
|
|
7235
|
+
b: 0
|
|
7236
|
+
},
|
|
7237
|
+
breath_timing: {
|
|
7238
|
+
rise_ms: 2080,
|
|
7239
|
+
hold_ms: 0,
|
|
7240
|
+
fall_ms: 2080,
|
|
7241
|
+
off_ms: 0
|
|
7242
|
+
}
|
|
7243
|
+
}
|
|
7244
|
+
],
|
|
7245
|
+
base64: "RWZmZWN0OiBicmVhdGggKDEgc2VnbWVudCnCmsKBwpLCgsKRwoLCkcKEwpHCgMKA"
|
|
7246
|
+
},
|
|
7247
|
+
{
|
|
7248
|
+
presetId: "red-strobe-3",
|
|
7249
|
+
displayName: "红色三闪",
|
|
7250
|
+
description: "红色短促频闪,体感接近三闪",
|
|
7251
|
+
repeat_times: 1,
|
|
7252
|
+
segments: [
|
|
7253
|
+
{
|
|
7254
|
+
mode: "strobe",
|
|
7255
|
+
duration_s: 1,
|
|
7256
|
+
interval_ms: 300,
|
|
7257
|
+
brightness: 192,
|
|
7258
|
+
color: {
|
|
7259
|
+
r: 255,
|
|
7260
|
+
g: 0,
|
|
7261
|
+
b: 0
|
|
7262
|
+
}
|
|
7263
|
+
}
|
|
7264
|
+
],
|
|
7265
|
+
base64: "RWZmZWN0OiBzdHJvYmUgKDEgc2VnbWVudCnCmsKCwoHCg8KEwpHCgMKA"
|
|
7266
|
+
},
|
|
7267
|
+
{
|
|
7268
|
+
presetId: "green-steady",
|
|
7269
|
+
displayName: "绿色常亮",
|
|
7270
|
+
description: "绿灯持续亮起 5 秒",
|
|
7271
|
+
repeat_times: 1,
|
|
7272
|
+
segments: [
|
|
7273
|
+
{
|
|
7274
|
+
mode: "steady",
|
|
7275
|
+
duration_s: 5,
|
|
7276
|
+
brightness: 192,
|
|
7277
|
+
color: {
|
|
7278
|
+
r: 0,
|
|
7279
|
+
g: 255,
|
|
7280
|
+
b: 0
|
|
7281
|
+
}
|
|
7282
|
+
}
|
|
7283
|
+
],
|
|
7284
|
+
base64: "RWZmZWN0OiBzdGVhZHkgKDEgc2VnbWVudCnCmsKDwoTChMKAwpHCgA=="
|
|
7285
|
+
},
|
|
7286
|
+
{
|
|
7287
|
+
presetId: "green-breath",
|
|
7288
|
+
displayName: "绿色呼吸",
|
|
7289
|
+
description: "绿色缓慢呼吸约两轮",
|
|
7290
|
+
repeat_times: 1,
|
|
7291
|
+
segments: [
|
|
7292
|
+
{
|
|
7293
|
+
mode: "breath",
|
|
7294
|
+
duration_s: 8,
|
|
7295
|
+
brightness: 192,
|
|
7296
|
+
color: {
|
|
7297
|
+
r: 0,
|
|
7298
|
+
g: 255,
|
|
7299
|
+
b: 0
|
|
7300
|
+
},
|
|
7301
|
+
breath_timing: {
|
|
7302
|
+
rise_ms: 2080,
|
|
7303
|
+
hold_ms: 0,
|
|
7304
|
+
fall_ms: 2080,
|
|
7305
|
+
off_ms: 0
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
],
|
|
7309
|
+
base64: "RWZmZWN0OiBicmVhdGggKDEgc2VnbWVudCnCmsKBwpLCgsKRwoLCkcKEwoDCkcKA"
|
|
7310
|
+
},
|
|
7311
|
+
{
|
|
7312
|
+
presetId: "blue-wave",
|
|
7313
|
+
displayName: "蓝色波浪",
|
|
7314
|
+
description: "蓝色从左到右流动一轮",
|
|
7315
|
+
repeat_times: 1,
|
|
7316
|
+
segments: [
|
|
7317
|
+
{
|
|
7318
|
+
mode: "wave",
|
|
7319
|
+
duration_s: 5,
|
|
7320
|
+
interval_ms: 200,
|
|
7321
|
+
brightness: 192,
|
|
7322
|
+
color: {
|
|
7323
|
+
r: 0,
|
|
7324
|
+
g: 0,
|
|
7325
|
+
b: 255
|
|
7326
|
+
},
|
|
7327
|
+
direction: "ltr",
|
|
7328
|
+
window: 2,
|
|
7329
|
+
background: {
|
|
7330
|
+
r: 0,
|
|
7331
|
+
g: 0,
|
|
7332
|
+
b: 0,
|
|
7333
|
+
brightness: 0
|
|
7334
|
+
}
|
|
7335
|
+
}
|
|
7336
|
+
],
|
|
7337
|
+
base64: "RWZmZWN0OiB3YXZlICgxIHNlZ21lbnQpwprCgMKEwoLChMKAwoDCkcKAwoHCgMKAwoDCgA=="
|
|
7338
|
+
},
|
|
7339
|
+
{
|
|
7340
|
+
presetId: "blue-breath",
|
|
7341
|
+
displayName: "蓝色呼吸",
|
|
7342
|
+
description: "蓝色缓慢呼吸约两轮",
|
|
7343
|
+
repeat_times: 1,
|
|
7344
|
+
segments: [
|
|
7345
|
+
{
|
|
7346
|
+
mode: "breath",
|
|
7347
|
+
duration_s: 8,
|
|
7348
|
+
brightness: 192,
|
|
7349
|
+
color: {
|
|
7350
|
+
r: 0,
|
|
7351
|
+
g: 0,
|
|
7352
|
+
b: 255
|
|
7353
|
+
},
|
|
7354
|
+
breath_timing: {
|
|
7355
|
+
rise_ms: 2080,
|
|
7356
|
+
hold_ms: 0,
|
|
7357
|
+
fall_ms: 2080,
|
|
7358
|
+
off_ms: 0
|
|
7359
|
+
}
|
|
7360
|
+
}
|
|
7361
|
+
],
|
|
7362
|
+
base64: "RWZmZWN0OiBicmVhdGggKDEgc2VnbWVudCnCmsKBwpLCgsKRwoLCkcKEwoDCgMKR"
|
|
7363
|
+
},
|
|
7364
|
+
{
|
|
7365
|
+
presetId: "yellow-strobe-3",
|
|
7366
|
+
displayName: "黄色三闪",
|
|
7367
|
+
description: "黄色短促频闪,体感接近三闪",
|
|
7368
|
+
repeat_times: 1,
|
|
7369
|
+
segments: [
|
|
7370
|
+
{
|
|
7371
|
+
mode: "strobe",
|
|
7372
|
+
duration_s: 1,
|
|
7373
|
+
interval_ms: 300,
|
|
7374
|
+
brightness: 192,
|
|
7375
|
+
color: {
|
|
7376
|
+
r: 255,
|
|
7377
|
+
g: 255,
|
|
7378
|
+
b: 0
|
|
7379
|
+
}
|
|
7380
|
+
}
|
|
7381
|
+
],
|
|
7382
|
+
base64: "RWZmZWN0OiBzdHJvYmUgKDEgc2VnbWVudCnCmsKCwoHCg8KEwpHCgsKA"
|
|
7383
|
+
}
|
|
7384
|
+
]
|
|
7385
|
+
};
|
|
7386
|
+
|
|
7387
|
+
// ../phone-notifications/src/tunnel/relay-client.ts
|
|
7388
|
+
var import_node_fs14 = require("node:fs");
|
|
7389
|
+
var import_node_path8 = require("node:path");
|
|
6741
7390
|
|
|
6742
7391
|
// ../../node_modules/ws/wrapper.mjs
|
|
6743
7392
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -6754,6 +7403,9 @@ var wrapper_default = import_websocket.default;
|
|
|
6754
7403
|
function previewText2(text, max = 500) {
|
|
6755
7404
|
return text.length <= max ? text : `${text.substring(0, max)}…`;
|
|
6756
7405
|
}
|
|
7406
|
+
function maskableSecret(credential) {
|
|
7407
|
+
return credential.query?.apiKey ?? Object.values(credential.headers ?? {})[0] ?? "";
|
|
7408
|
+
}
|
|
6757
7409
|
var HANDSHAKE_TIMEOUT_MS = 15000;
|
|
6758
7410
|
var CONNECT_WATCHDOG_MS = 20000;
|
|
6759
7411
|
var SEND_SKIPPED_LOG_INTERVAL_MS = 30000;
|
|
@@ -6785,8 +7437,8 @@ class RelayClient {
|
|
|
6785
7437
|
lastDisconnectReason
|
|
6786
7438
|
};
|
|
6787
7439
|
try {
|
|
6788
|
-
|
|
6789
|
-
|
|
7440
|
+
import_node_fs14.mkdirSync(import_node_path8.dirname(this.opts.statusFilePath), { recursive: true });
|
|
7441
|
+
import_node_fs14.writeFileSync(this.opts.statusFilePath, JSON.stringify(info, null, 2));
|
|
6790
7442
|
} catch {}
|
|
6791
7443
|
}
|
|
6792
7444
|
onInbound(handler) {
|
|
@@ -6910,8 +7562,17 @@ class RelayClient {
|
|
|
6910
7562
|
if (this.aborted)
|
|
6911
7563
|
return;
|
|
6912
7564
|
this.cleanup(true);
|
|
6913
|
-
|
|
6914
|
-
|
|
7565
|
+
let credential;
|
|
7566
|
+
try {
|
|
7567
|
+
credential = await this.opts.credentialProvider();
|
|
7568
|
+
} catch (err) {
|
|
7569
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7570
|
+
this.opts.logger.warn(`Relay tunnel: failed to resolve credential, scheduling reconnect: ${message}`);
|
|
7571
|
+
this.writeStatus("disconnected", `credential-error: ${message}`);
|
|
7572
|
+
this.scheduleReconnect();
|
|
7573
|
+
return;
|
|
7574
|
+
}
|
|
7575
|
+
this.opts.logger.info(`Relay tunnel: connecting to ${redactUrlSecrets(this.opts.tunnelUrl)} (attempt=${this.reconnectAttempt}, heartbeat=${this.opts.heartbeatSec}s, apiKey=${maskSecret(maskableSecret(credential))})`);
|
|
6915
7576
|
this.writeStatus("connecting");
|
|
6916
7577
|
return new Promise((resolve) => {
|
|
6917
7578
|
let settled = false;
|
|
@@ -6922,13 +7583,13 @@ class RelayClient {
|
|
|
6922
7583
|
}
|
|
6923
7584
|
};
|
|
6924
7585
|
const wsUrl = new URL(this.opts.tunnelUrl);
|
|
6925
|
-
|
|
6926
|
-
wsUrl.searchParams.
|
|
7586
|
+
for (const [key, value] of Object.entries(credential.query ?? {})) {
|
|
7587
|
+
if (!wsUrl.searchParams.get(key)) {
|
|
7588
|
+
wsUrl.searchParams.set(key, value);
|
|
7589
|
+
}
|
|
6927
7590
|
}
|
|
6928
7591
|
const ws = new wrapper_default(wsUrl.toString(), {
|
|
6929
|
-
headers: {
|
|
6930
|
-
"X-Api-Key-Id": rawApiKey
|
|
6931
|
-
},
|
|
7592
|
+
headers: credential.headers ?? {},
|
|
6932
7593
|
handshakeTimeout: HANDSHAKE_TIMEOUT_MS
|
|
6933
7594
|
});
|
|
6934
7595
|
this.ws = ws;
|
|
@@ -7167,92 +7828,16 @@ class RelayClient {
|
|
|
7167
7828
|
// ../phone-notifications/src/tunnel/proxy.ts
|
|
7168
7829
|
var import_node_crypto7 = require("node:crypto");
|
|
7169
7830
|
|
|
7170
|
-
// ../phone-notifications/src/host.ts
|
|
7171
|
-
var import_node_fs12 = require("node:fs");
|
|
7172
|
-
var import_node_path8 = require("node:path");
|
|
7173
|
-
function trimToUndefined(value) {
|
|
7174
|
-
if (typeof value !== "string") {
|
|
7175
|
-
return;
|
|
7176
|
-
}
|
|
7177
|
-
const trimmed = value.trim();
|
|
7178
|
-
return trimmed || undefined;
|
|
7179
|
-
}
|
|
7180
|
-
function homeDir() {
|
|
7181
|
-
return process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
7182
|
-
}
|
|
7183
|
-
function expandUserPath(value) {
|
|
7184
|
-
if (!value) {
|
|
7185
|
-
return;
|
|
7186
|
-
}
|
|
7187
|
-
if (value === "~") {
|
|
7188
|
-
return homeDir();
|
|
7189
|
-
}
|
|
7190
|
-
if (value.startsWith("~/")) {
|
|
7191
|
-
return import_node_path8.join(homeDir(), value.slice(2));
|
|
7192
|
-
}
|
|
7193
|
-
return value;
|
|
7194
|
-
}
|
|
7195
|
-
function candidateMetaPaths() {
|
|
7196
|
-
const home = homeDir();
|
|
7197
|
-
return [
|
|
7198
|
-
import_node_path8.join(home, ".qclaw", "qclaw.json"),
|
|
7199
|
-
import_node_path8.join(home, ".qclow", "qclaw.json")
|
|
7200
|
-
];
|
|
7201
|
-
}
|
|
7202
|
-
function loadQClawMeta() {
|
|
7203
|
-
for (const metaPath of candidateMetaPaths()) {
|
|
7204
|
-
if (!import_node_fs12.existsSync(metaPath)) {
|
|
7205
|
-
continue;
|
|
7206
|
-
}
|
|
7207
|
-
try {
|
|
7208
|
-
const parsed = JSON.parse(import_node_fs12.readFileSync(metaPath, "utf-8"));
|
|
7209
|
-
return {
|
|
7210
|
-
stateDir: expandUserPath(trimToUndefined(parsed?.stateDir)),
|
|
7211
|
-
configPath: expandUserPath(trimToUndefined(parsed?.configPath))
|
|
7212
|
-
};
|
|
7213
|
-
} catch {}
|
|
7214
|
-
}
|
|
7215
|
-
return;
|
|
7216
|
-
}
|
|
7217
|
-
function resolveStateDirFromEnv() {
|
|
7218
|
-
return expandUserPath(trimToUndefined(process.env.OPENCLAW_STATE_DIR) ?? trimToUndefined(process.env.QCLAW_STATE_DIR) ?? trimToUndefined(process.env.OPENCLAW_ROOT) ?? trimToUndefined(process.env.OPENCLAW_HOME) ?? trimToUndefined(process.env.QCLAW_HOME));
|
|
7219
|
-
}
|
|
7220
|
-
function hasOpenClawMarkers() {
|
|
7221
|
-
const baseDir = import_node_path8.join(homeDir(), ".openclaw");
|
|
7222
|
-
return [
|
|
7223
|
-
import_node_path8.join(baseDir, "openclaw.json"),
|
|
7224
|
-
import_node_path8.join(baseDir, "credentials.json"),
|
|
7225
|
-
import_node_path8.join(baseDir, "extensions")
|
|
7226
|
-
].some((candidate) => import_node_fs12.existsSync(candidate));
|
|
7227
|
-
}
|
|
7228
|
-
function resolveStateDir() {
|
|
7229
|
-
const envDir = resolveStateDirFromEnv();
|
|
7230
|
-
if (envDir) {
|
|
7231
|
-
return envDir;
|
|
7232
|
-
}
|
|
7233
|
-
if (hasOpenClawMarkers()) {
|
|
7234
|
-
return import_node_path8.join(homeDir(), ".openclaw");
|
|
7235
|
-
}
|
|
7236
|
-
const meta = loadQClawMeta();
|
|
7237
|
-
if (meta?.stateDir) {
|
|
7238
|
-
return meta.stateDir;
|
|
7239
|
-
}
|
|
7240
|
-
if (meta?.configPath) {
|
|
7241
|
-
return import_node_path8.dirname(meta.configPath);
|
|
7242
|
-
}
|
|
7243
|
-
return import_node_path8.join(homeDir(), ".openclaw");
|
|
7244
|
-
}
|
|
7245
|
-
|
|
7246
7831
|
// ../phone-notifications/src/tunnel/device-identity.ts
|
|
7247
|
-
var
|
|
7248
|
-
var
|
|
7249
|
-
var
|
|
7832
|
+
var crypto = __toESM(require("node:crypto"));
|
|
7833
|
+
var fs = __toESM(require("node:fs"));
|
|
7834
|
+
var path = __toESM(require("node:path"));
|
|
7250
7835
|
var ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
7251
7836
|
function base64UrlEncode(buf) {
|
|
7252
7837
|
return buf.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/g, "");
|
|
7253
7838
|
}
|
|
7254
7839
|
function derivePublicKeyRaw(publicKeyPem) {
|
|
7255
|
-
const spki =
|
|
7840
|
+
const spki = crypto.createPublicKey(publicKeyPem).export({
|
|
7256
7841
|
type: "spki",
|
|
7257
7842
|
format: "der"
|
|
7258
7843
|
});
|
|
@@ -7262,14 +7847,14 @@ function derivePublicKeyRaw(publicKeyPem) {
|
|
|
7262
7847
|
}
|
|
7263
7848
|
function fingerprintPublicKey(publicKeyPem) {
|
|
7264
7849
|
const raw = derivePublicKeyRaw(publicKeyPem);
|
|
7265
|
-
return
|
|
7850
|
+
return crypto.createHash("sha256").update(raw).digest("hex");
|
|
7266
7851
|
}
|
|
7267
7852
|
function publicKeyRawBase64UrlFromPem(publicKeyPem) {
|
|
7268
7853
|
return base64UrlEncode(derivePublicKeyRaw(publicKeyPem));
|
|
7269
7854
|
}
|
|
7270
7855
|
function signDevicePayload(privateKeyPem, payload) {
|
|
7271
|
-
const key =
|
|
7272
|
-
return base64UrlEncode(
|
|
7856
|
+
const key = crypto.createPrivateKey(privateKeyPem);
|
|
7857
|
+
return base64UrlEncode(crypto.sign(null, Buffer.from(payload, "utf8"), key));
|
|
7273
7858
|
}
|
|
7274
7859
|
function buildDeviceAuthPayload(params) {
|
|
7275
7860
|
const scopes = params.scopes.join(",");
|
|
@@ -7290,10 +7875,10 @@ function resolveClientStateDir(stateDir) {
|
|
|
7290
7875
|
return stateDir ?? resolveStateDir();
|
|
7291
7876
|
}
|
|
7292
7877
|
function ensureDir2(filePath) {
|
|
7293
|
-
|
|
7878
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
7294
7879
|
}
|
|
7295
7880
|
function resolveIdentityPath(stateDir) {
|
|
7296
|
-
return
|
|
7881
|
+
return path.join(stateDir, "identity", "device.json");
|
|
7297
7882
|
}
|
|
7298
7883
|
function normalizeDeviceAuthRole(role) {
|
|
7299
7884
|
return role.trim();
|
|
@@ -7309,13 +7894,13 @@ function normalizeDeviceAuthScopes(scopes) {
|
|
|
7309
7894
|
return [...out].sort();
|
|
7310
7895
|
}
|
|
7311
7896
|
function resolveDeviceAuthPath(stateDir) {
|
|
7312
|
-
return
|
|
7897
|
+
return path.join(stateDir, "identity", "device-auth.json");
|
|
7313
7898
|
}
|
|
7314
7899
|
function readDeviceAuthStore(filePath) {
|
|
7315
7900
|
try {
|
|
7316
|
-
if (!
|
|
7901
|
+
if (!fs.existsSync(filePath))
|
|
7317
7902
|
return null;
|
|
7318
|
-
const raw =
|
|
7903
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
7319
7904
|
const parsed = JSON.parse(raw);
|
|
7320
7905
|
if (parsed?.version !== 1 || typeof parsed.deviceId !== "string")
|
|
7321
7906
|
return null;
|
|
@@ -7328,12 +7913,12 @@ function readDeviceAuthStore(filePath) {
|
|
|
7328
7913
|
}
|
|
7329
7914
|
function writeDeviceAuthStore(filePath, store) {
|
|
7330
7915
|
ensureDir2(filePath);
|
|
7331
|
-
|
|
7916
|
+
fs.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}
|
|
7332
7917
|
`, {
|
|
7333
7918
|
mode: 384
|
|
7334
7919
|
});
|
|
7335
7920
|
try {
|
|
7336
|
-
|
|
7921
|
+
fs.chmodSync(filePath, 384);
|
|
7337
7922
|
} catch {}
|
|
7338
7923
|
}
|
|
7339
7924
|
function loadDeviceAuthToken(params) {
|
|
@@ -7383,8 +7968,8 @@ function clearDeviceAuthToken(params) {
|
|
|
7383
7968
|
function loadOrCreateDeviceIdentity(stateDir) {
|
|
7384
7969
|
const filePath = resolveIdentityPath(stateDir);
|
|
7385
7970
|
try {
|
|
7386
|
-
if (
|
|
7387
|
-
const raw =
|
|
7971
|
+
if (fs.existsSync(filePath)) {
|
|
7972
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
7388
7973
|
const parsed = JSON.parse(raw);
|
|
7389
7974
|
if (parsed?.version === 1 && typeof parsed.deviceId === "string" && typeof parsed.publicKeyPem === "string" && typeof parsed.privateKeyPem === "string") {
|
|
7390
7975
|
const derivedId = fingerprintPublicKey(parsed.publicKeyPem);
|
|
@@ -7396,7 +7981,7 @@ function loadOrCreateDeviceIdentity(stateDir) {
|
|
|
7396
7981
|
}
|
|
7397
7982
|
}
|
|
7398
7983
|
} catch {}
|
|
7399
|
-
const { publicKey, privateKey } =
|
|
7984
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
7400
7985
|
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString();
|
|
7401
7986
|
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();
|
|
7402
7987
|
const identity = {
|
|
@@ -7404,14 +7989,14 @@ function loadOrCreateDeviceIdentity(stateDir) {
|
|
|
7404
7989
|
publicKeyPem,
|
|
7405
7990
|
privateKeyPem
|
|
7406
7991
|
};
|
|
7407
|
-
|
|
7992
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
7408
7993
|
const stored = {
|
|
7409
7994
|
version: 1,
|
|
7410
7995
|
...identity,
|
|
7411
7996
|
createdAtMs: Date.now()
|
|
7412
7997
|
};
|
|
7413
7998
|
ensureDir2(filePath);
|
|
7414
|
-
|
|
7999
|
+
fs.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}
|
|
7415
8000
|
`, {
|
|
7416
8001
|
mode: 384
|
|
7417
8002
|
});
|
|
@@ -8212,6 +8797,10 @@ class TunnelProxy {
|
|
|
8212
8797
|
ws.send(JSON.stringify(connectReq));
|
|
8213
8798
|
}
|
|
8214
8799
|
}
|
|
8800
|
+
|
|
8801
|
+
// src/shared.ts
|
|
8802
|
+
var LIGHT_PRESETS_V1 = presets_v1_default;
|
|
8803
|
+
|
|
8215
8804
|
// src/notification/query.ts
|
|
8216
8805
|
var ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(Z|[+-]\d{2}:\d{2})$/;
|
|
8217
8806
|
function parseIso(value, name) {
|
|
@@ -8256,7 +8845,7 @@ function buildQueryOptions(opts, defaultLimit = 100) {
|
|
|
8256
8845
|
};
|
|
8257
8846
|
}
|
|
8258
8847
|
async function queryNotifications(paths, options) {
|
|
8259
|
-
if (!
|
|
8848
|
+
if (!import_node_fs16.existsSync(paths.notifications))
|
|
8260
8849
|
return [];
|
|
8261
8850
|
return collectMatchingNotifications(paths.notifications, options);
|
|
8262
8851
|
}
|
|
@@ -8350,26 +8939,26 @@ function notificationUnread() {
|
|
|
8350
8939
|
}
|
|
8351
8940
|
function tzOffset() {
|
|
8352
8941
|
const min = -new Date().getTimezoneOffset();
|
|
8353
|
-
const
|
|
8942
|
+
const sign2 = min >= 0 ? "+" : "-";
|
|
8354
8943
|
const abs = Math.abs(min);
|
|
8355
8944
|
const hh = String(Math.floor(abs / 60)).padStart(2, "0");
|
|
8356
8945
|
const mm = String(abs % 60).padStart(2, "0");
|
|
8357
|
-
return `${
|
|
8946
|
+
return `${sign2}${hh}:${mm}`;
|
|
8358
8947
|
}
|
|
8359
8948
|
|
|
8360
8949
|
// src/notification/sync.ts
|
|
8361
|
-
var
|
|
8362
|
-
var
|
|
8950
|
+
var import_node_fs17 = require("node:fs");
|
|
8951
|
+
var import_node_path14 = require("node:path");
|
|
8363
8952
|
var SYNC_FETCH_LIMIT = 300;
|
|
8364
8953
|
function checkpointPath(dir) {
|
|
8365
|
-
return
|
|
8954
|
+
return import_node_path14.join(dir, ".checkpoint.json");
|
|
8366
8955
|
}
|
|
8367
8956
|
function readCheckpoint(dir) {
|
|
8368
8957
|
const p = checkpointPath(dir);
|
|
8369
|
-
if (!
|
|
8958
|
+
if (!import_node_fs17.existsSync(p))
|
|
8370
8959
|
return {};
|
|
8371
8960
|
try {
|
|
8372
|
-
return JSON.parse(
|
|
8961
|
+
return JSON.parse(import_node_fs17.readFileSync(p, "utf-8"));
|
|
8373
8962
|
} catch {
|
|
8374
8963
|
return {};
|
|
8375
8964
|
}
|
|
@@ -8378,11 +8967,11 @@ function writeCheckpoint(dir, data) {
|
|
|
8378
8967
|
writeJsonFile(checkpointPath(dir), data);
|
|
8379
8968
|
}
|
|
8380
8969
|
function listDateKeys(dir) {
|
|
8381
|
-
if (!
|
|
8970
|
+
if (!import_node_fs17.existsSync(dir))
|
|
8382
8971
|
return [];
|
|
8383
8972
|
const pattern = /^(\d{4}-\d{2}-\d{2})\.json$/;
|
|
8384
8973
|
const keys = [];
|
|
8385
|
-
for (const entry of
|
|
8974
|
+
for (const entry of import_node_fs17.readdirSync(dir, { withFileTypes: true })) {
|
|
8386
8975
|
if (!entry.isFile())
|
|
8387
8976
|
continue;
|
|
8388
8977
|
const m = pattern.exec(entry.name);
|
|
@@ -8392,11 +8981,11 @@ function listDateKeys(dir) {
|
|
|
8392
8981
|
return keys.sort((a, b) => b.localeCompare(a));
|
|
8393
8982
|
}
|
|
8394
8983
|
function readDateFile(dir, dateKey) {
|
|
8395
|
-
const filePath =
|
|
8396
|
-
if (!
|
|
8984
|
+
const filePath = import_node_path14.join(dir, `${dateKey}.json`);
|
|
8985
|
+
if (!import_node_fs17.existsSync(filePath))
|
|
8397
8986
|
return [];
|
|
8398
8987
|
try {
|
|
8399
|
-
const parsed = JSON.parse(
|
|
8988
|
+
const parsed = JSON.parse(import_node_fs17.readFileSync(filePath, "utf-8"));
|
|
8400
8989
|
return Array.isArray(parsed) ? parsed : [];
|
|
8401
8990
|
} catch {
|
|
8402
8991
|
return [];
|
|
@@ -8512,10 +9101,10 @@ function syncCommit(ctx, _args, opts) {
|
|
|
8512
9101
|
}
|
|
8513
9102
|
|
|
8514
9103
|
// src/commands/recording.ts
|
|
8515
|
-
var
|
|
8516
|
-
var
|
|
9104
|
+
var import_node_fs18 = require("node:fs");
|
|
9105
|
+
var import_node_path15 = require("node:path");
|
|
8517
9106
|
function readIndex(ctx) {
|
|
8518
|
-
if (!
|
|
9107
|
+
if (!import_node_fs18.existsSync(ctx.paths.recordings))
|
|
8519
9108
|
return [];
|
|
8520
9109
|
return readRecordingIndex(ctx.paths.recordings);
|
|
8521
9110
|
}
|
|
@@ -8594,30 +9183,30 @@ async function recordingSetupAsr(ctx, _args, opts) {
|
|
|
8594
9183
|
if (apiKey)
|
|
8595
9184
|
config.apiKey = apiKey;
|
|
8596
9185
|
ensureDir(ctx.paths.recordings);
|
|
8597
|
-
const path7 =
|
|
9186
|
+
const path7 = import_node_path15.join(ctx.paths.recordings, "asr-config.json");
|
|
8598
9187
|
writeJsonFile(path7, config);
|
|
8599
9188
|
return { ok: true, path: path7, provider, keyConfigured: Boolean(apiKey) };
|
|
8600
9189
|
}
|
|
8601
9190
|
|
|
8602
9191
|
// src/image/storage.ts
|
|
8603
|
-
var
|
|
8604
|
-
var
|
|
9192
|
+
var import_node_fs19 = require("node:fs");
|
|
9193
|
+
var import_node_path16 = require("node:path");
|
|
8605
9194
|
function imagesIndexPath(paths) {
|
|
8606
|
-
return
|
|
9195
|
+
return import_node_path16.join(paths.images, "index.json");
|
|
8607
9196
|
}
|
|
8608
9197
|
function readImageIndex(paths) {
|
|
8609
9198
|
const indexPath = imagesIndexPath(paths);
|
|
8610
|
-
if (!
|
|
9199
|
+
if (!import_node_fs19.existsSync(indexPath))
|
|
8611
9200
|
return [];
|
|
8612
9201
|
try {
|
|
8613
|
-
const raw = JSON.parse(
|
|
9202
|
+
const raw = JSON.parse(import_node_fs19.readFileSync(indexPath, "utf-8"));
|
|
8614
9203
|
return Array.isArray(raw?.images) ? raw.images : [];
|
|
8615
9204
|
} catch {
|
|
8616
9205
|
return [];
|
|
8617
9206
|
}
|
|
8618
9207
|
}
|
|
8619
9208
|
function resolveImageFile(paths, relative) {
|
|
8620
|
-
return
|
|
9209
|
+
return import_node_path16.isAbsolute(relative) ? relative : import_node_path16.join(paths.images, relative);
|
|
8621
9210
|
}
|
|
8622
9211
|
|
|
8623
9212
|
// src/commands/image.ts
|
|
@@ -8671,17 +9260,17 @@ function imageLatest(ctx) {
|
|
|
8671
9260
|
}
|
|
8672
9261
|
|
|
8673
9262
|
// src/log/reader.ts
|
|
8674
|
-
var
|
|
8675
|
-
var
|
|
9263
|
+
var import_node_fs20 = require("node:fs");
|
|
9264
|
+
var import_node_path17 = require("node:path");
|
|
8676
9265
|
var LINE_RE = /^(\d{4}-\d{2}-\d{2})(T\S+)?\s+\[(\w+)\]\s+(.*)$/;
|
|
8677
9266
|
function logFiles(daemonLog) {
|
|
8678
|
-
const dir =
|
|
8679
|
-
const base =
|
|
8680
|
-
if (!
|
|
9267
|
+
const dir = import_node_path17.dirname(daemonLog);
|
|
9268
|
+
const base = import_node_path17.basename(daemonLog);
|
|
9269
|
+
if (!import_node_fs20.existsSync(dir))
|
|
8681
9270
|
return [];
|
|
8682
|
-
const rotated =
|
|
9271
|
+
const rotated = import_node_fs20.readdirSync(dir).filter((f) => f.startsWith(`${base}.`) && /\.\d{4}-\d{2}-\d{2}$/.test(f)).sort((a, b) => b.localeCompare(a)).map((f) => import_node_path17.join(dir, f));
|
|
8683
9272
|
const files = [];
|
|
8684
|
-
if (
|
|
9273
|
+
if (import_node_fs20.existsSync(daemonLog))
|
|
8685
9274
|
files.push(daemonLog);
|
|
8686
9275
|
files.push(...rotated);
|
|
8687
9276
|
return files;
|
|
@@ -8703,7 +9292,7 @@ function searchLogs(daemonLog, query) {
|
|
|
8703
9292
|
const level = query.level?.toLowerCase();
|
|
8704
9293
|
const results = [];
|
|
8705
9294
|
for (const file of logFiles(daemonLog)) {
|
|
8706
|
-
const content =
|
|
9295
|
+
const content = import_node_fs20.readFileSync(file, "utf-8");
|
|
8707
9296
|
const lines = content.split(`
|
|
8708
9297
|
`).filter(Boolean).reverse();
|
|
8709
9298
|
for (const raw of lines) {
|
|
@@ -8748,34 +9337,34 @@ function logErrors(ctx) {
|
|
|
8748
9337
|
}
|
|
8749
9338
|
|
|
8750
9339
|
// src/commands/migrate.ts
|
|
8751
|
-
var
|
|
9340
|
+
var import_node_fs21 = require("node:fs");
|
|
8752
9341
|
var import_node_os6 = require("node:os");
|
|
8753
|
-
var
|
|
9342
|
+
var import_node_path18 = require("node:path");
|
|
8754
9343
|
function countFiles(dir) {
|
|
8755
|
-
if (!
|
|
9344
|
+
if (!import_node_fs21.existsSync(dir))
|
|
8756
9345
|
return 0;
|
|
8757
9346
|
try {
|
|
8758
|
-
return
|
|
9347
|
+
return import_node_fs21.readdirSync(dir, { recursive: true }).length;
|
|
8759
9348
|
} catch {
|
|
8760
9349
|
return 0;
|
|
8761
9350
|
}
|
|
8762
9351
|
}
|
|
8763
9352
|
function migrateFromOpenclaw(ctx, _args, opts) {
|
|
8764
|
-
const sourceRoot = opts.source ??
|
|
8765
|
-
const pluginRoot =
|
|
9353
|
+
const sourceRoot = opts.source ?? import_node_path18.join(import_node_os6.homedir(), ".openclaw");
|
|
9354
|
+
const pluginRoot = import_node_path18.join(sourceRoot, "plugins", "phone-notifications");
|
|
8766
9355
|
const plans = [
|
|
8767
|
-
{ name: "notifications", source:
|
|
8768
|
-
{ name: "recordings", source:
|
|
8769
|
-
{ name: "light-rules", source:
|
|
8770
|
-
{ name: "images", source:
|
|
8771
|
-
].map((p) => ({ ...p, exists:
|
|
8772
|
-
const srcCreds = readJsonFile(
|
|
9356
|
+
{ name: "notifications", source: import_node_path18.join(pluginRoot, "notifications"), target: ctx.paths.notifications },
|
|
9357
|
+
{ name: "recordings", source: import_node_path18.join(pluginRoot, "recordings"), target: ctx.paths.recordings },
|
|
9358
|
+
{ name: "light-rules", source: import_node_path18.join(pluginRoot, "light-rules"), target: ctx.paths.lightRules },
|
|
9359
|
+
{ name: "images", source: import_node_path18.join(pluginRoot, "images"), target: ctx.paths.images }
|
|
9360
|
+
].map((p) => ({ ...p, exists: import_node_fs21.existsSync(p.source), fileCount: countFiles(p.source) }));
|
|
9361
|
+
const srcCreds = readJsonFile(import_node_path18.join(sourceRoot, "credentials.json"));
|
|
8773
9362
|
const srcApiKey = typeof srcCreds?.[API_KEY_FIELD] === "string" ? srcCreds[API_KEY_FIELD] : undefined;
|
|
8774
9363
|
const sharedPath = sharedCredentialsPath();
|
|
8775
9364
|
const existingShared = readJsonFile(sharedPath);
|
|
8776
9365
|
const apiKeyAction = !srcApiKey ? "none" : existingShared?.[API_KEY_FIELD] ? "skip-existing" : "copy";
|
|
8777
9366
|
const backupDir = `${ctx.paths.dir}.bak-${Date.now()}`;
|
|
8778
|
-
const willBackup =
|
|
9367
|
+
const willBackup = import_node_fs21.existsSync(ctx.paths.dir) && import_node_fs21.readdirSync(ctx.paths.dir).length > 0;
|
|
8779
9368
|
if (opts.dryRun) {
|
|
8780
9369
|
return {
|
|
8781
9370
|
ok: true,
|
|
@@ -8789,14 +9378,14 @@ function migrateFromOpenclaw(ctx, _args, opts) {
|
|
|
8789
9378
|
};
|
|
8790
9379
|
}
|
|
8791
9380
|
if (willBackup) {
|
|
8792
|
-
|
|
9381
|
+
import_node_fs21.cpSync(ctx.paths.dir, backupDir, { recursive: true });
|
|
8793
9382
|
}
|
|
8794
9383
|
ensureDir(ctx.paths.dir);
|
|
8795
9384
|
const migrated = [];
|
|
8796
9385
|
for (const plan of plans) {
|
|
8797
9386
|
if (!plan.exists)
|
|
8798
9387
|
continue;
|
|
8799
|
-
|
|
9388
|
+
import_node_fs21.cpSync(plan.source, plan.target, { recursive: true });
|
|
8800
9389
|
migrated.push(plan.name);
|
|
8801
9390
|
}
|
|
8802
9391
|
let apiKeyResult = apiKeyAction;
|
|
@@ -8869,7 +9458,7 @@ async function updateSelf(_ctx, _args, opts) {
|
|
|
8869
9458
|
}
|
|
8870
9459
|
|
|
8871
9460
|
// src/commands/doctor.ts
|
|
8872
|
-
var
|
|
9461
|
+
var import_node_fs22 = require("node:fs");
|
|
8873
9462
|
var MIN_NODE = [22, 12, 0];
|
|
8874
9463
|
function checkNode() {
|
|
8875
9464
|
const parts = process.versions.node.split(".").map(Number);
|
|
@@ -8889,7 +9478,7 @@ function checkNode() {
|
|
|
8889
9478
|
};
|
|
8890
9479
|
}
|
|
8891
9480
|
function checkDir(name, dir, fix) {
|
|
8892
|
-
if (!
|
|
9481
|
+
if (!import_node_fs22.existsSync(dir)) {
|
|
8893
9482
|
if (fix) {
|
|
8894
9483
|
ensureDir(dir);
|
|
8895
9484
|
return { name, status: "ok", detail: `已创建 ${dir}` };
|
|
@@ -8897,11 +9486,11 @@ function checkDir(name, dir, fix) {
|
|
|
8897
9486
|
return { name, status: "warn", detail: `目录不存在:${dir}(--fix 可创建)` };
|
|
8898
9487
|
}
|
|
8899
9488
|
try {
|
|
8900
|
-
|
|
9489
|
+
import_node_fs22.accessSync(dir, import_node_fs22.constants.R_OK | import_node_fs22.constants.W_OK);
|
|
8901
9490
|
} catch {
|
|
8902
9491
|
return { name, status: "fail", detail: `目录不可读写:${dir}` };
|
|
8903
9492
|
}
|
|
8904
|
-
const mode =
|
|
9493
|
+
const mode = import_node_fs22.statSync(dir).mode & 511;
|
|
8905
9494
|
const tooOpen = (mode & 63) !== 0;
|
|
8906
9495
|
return {
|
|
8907
9496
|
name,
|
|
@@ -8963,47 +9552,161 @@ function doctor(ctx, _args, opts) {
|
|
|
8963
9552
|
}
|
|
8964
9553
|
|
|
8965
9554
|
// src/commands/skills.ts
|
|
8966
|
-
var
|
|
9555
|
+
var import_node_fs23 = require("node:fs");
|
|
8967
9556
|
var import_node_url = require("node:url");
|
|
8968
9557
|
var import_node_os7 = require("node:os");
|
|
8969
|
-
var
|
|
8970
|
-
|
|
8971
|
-
|
|
9558
|
+
var import_node_path19 = require("node:path");
|
|
9559
|
+
var AGENT_IDS = ["claude", "codex"];
|
|
9560
|
+
var INSTALL_AGENT_IDS = ["auto", "custom", ...AGENT_IDS];
|
|
9561
|
+
function trimEnv(name) {
|
|
9562
|
+
const value = process.env[name]?.trim();
|
|
9563
|
+
return value ? value : undefined;
|
|
9564
|
+
}
|
|
9565
|
+
function claudeHomeDir() {
|
|
9566
|
+
return import_node_path19.join(import_node_os7.homedir(), ".claude");
|
|
9567
|
+
}
|
|
9568
|
+
function codexHomeDir() {
|
|
9569
|
+
return trimEnv("CODEX_HOME") ?? import_node_path19.join(import_node_os7.homedir(), ".codex");
|
|
9570
|
+
}
|
|
9571
|
+
var AGENT_ADAPTERS = [
|
|
9572
|
+
{
|
|
9573
|
+
id: "claude",
|
|
9574
|
+
label: "Claude Code",
|
|
9575
|
+
homeDir: claudeHomeDir,
|
|
9576
|
+
skillsDir: () => import_node_path19.join(claudeHomeDir(), "skills")
|
|
9577
|
+
},
|
|
9578
|
+
{
|
|
9579
|
+
id: "codex",
|
|
9580
|
+
label: "Codex",
|
|
9581
|
+
homeDir: codexHomeDir,
|
|
9582
|
+
skillsDir: () => import_node_path19.join(codexHomeDir(), "skills"),
|
|
9583
|
+
envHint: "CODEX_HOME"
|
|
9584
|
+
}
|
|
9585
|
+
];
|
|
9586
|
+
function getAgentAdapter(id) {
|
|
9587
|
+
const adapter = AGENT_ADAPTERS.find((a) => a.id === id);
|
|
9588
|
+
if (!adapter) {
|
|
9589
|
+
throw new YoooclawError(ErrorCode.INVALID_ARGUMENT, `不支持的 agent:${id}`, { hint: `可选值:${INSTALL_AGENT_IDS.join(" | ")}` });
|
|
9590
|
+
}
|
|
9591
|
+
return adapter;
|
|
9592
|
+
}
|
|
9593
|
+
function normalizeAgent(raw) {
|
|
9594
|
+
const agent = typeof raw === "string" && raw.trim() ? raw.trim() : "auto";
|
|
9595
|
+
if (INSTALL_AGENT_IDS.includes(agent)) {
|
|
9596
|
+
return agent;
|
|
9597
|
+
}
|
|
9598
|
+
throw new YoooclawError(ErrorCode.INVALID_ARGUMENT, `不支持的 agent:${agent}`, { hint: `可选值:${INSTALL_AGENT_IDS.join(" | ")}` });
|
|
9599
|
+
}
|
|
9600
|
+
function hasPath(path7) {
|
|
9601
|
+
return import_node_fs23.existsSync(path7);
|
|
9602
|
+
}
|
|
9603
|
+
function listSkillTargetCandidates() {
|
|
9604
|
+
return AGENT_ADAPTERS.map((adapter) => {
|
|
9605
|
+
const homeDir2 = adapter.homeDir();
|
|
9606
|
+
const target = adapter.skillsDir();
|
|
9607
|
+
const hasHome = hasPath(homeDir2);
|
|
9608
|
+
const hasTarget = hasPath(target);
|
|
9609
|
+
const envValue = adapter.envHint ? trimEnv(adapter.envHint) : undefined;
|
|
9610
|
+
return {
|
|
9611
|
+
agent: adapter.id,
|
|
9612
|
+
label: adapter.label,
|
|
9613
|
+
homeDir: homeDir2,
|
|
9614
|
+
target,
|
|
9615
|
+
detected: hasHome || hasTarget || Boolean(envValue),
|
|
9616
|
+
reason: hasTarget ? "skills 目录已存在" : hasHome ? "agent 配置目录已存在" : envValue && adapter.envHint ? `${adapter.envHint} 已设置` : adapter.envHint ? `未发现配置目录;可设置 ${adapter.envHint} 或显式传 --agent ${adapter.id}` : `未发现配置目录;可显式传 --agent ${adapter.id}`,
|
|
9617
|
+
installCommand: `yoooclaw skills install --agent ${adapter.id}`
|
|
9618
|
+
};
|
|
9619
|
+
});
|
|
9620
|
+
}
|
|
9621
|
+
function resolveTargetSelection(opts) {
|
|
9622
|
+
const explicitTarget = opts.target?.trim();
|
|
9623
|
+
const agent = normalizeAgent(opts.agent);
|
|
9624
|
+
if (explicitTarget) {
|
|
9625
|
+
if (agent === "auto" || agent === "custom") {
|
|
9626
|
+
return {
|
|
9627
|
+
agent: "custom",
|
|
9628
|
+
agentLabel: "Custom",
|
|
9629
|
+
target: explicitTarget,
|
|
9630
|
+
source: "explicit-target"
|
|
9631
|
+
};
|
|
9632
|
+
}
|
|
9633
|
+
const adapter = getAgentAdapter(agent);
|
|
9634
|
+
return {
|
|
9635
|
+
agent,
|
|
9636
|
+
agentLabel: adapter.label,
|
|
9637
|
+
target: explicitTarget,
|
|
9638
|
+
source: "explicit-target"
|
|
9639
|
+
};
|
|
9640
|
+
}
|
|
9641
|
+
if (agent === "custom") {
|
|
9642
|
+
throw new YoooclawError(ErrorCode.INVALID_ARGUMENT, "`--agent custom` 需要同时传 `--target <dir>`", { hint: "例如:yoooclaw skills install --agent custom --target ~/.config/agent/skills" });
|
|
9643
|
+
}
|
|
9644
|
+
if (agent !== "auto") {
|
|
9645
|
+
const adapter = getAgentAdapter(agent);
|
|
9646
|
+
return {
|
|
9647
|
+
agent,
|
|
9648
|
+
agentLabel: adapter.label,
|
|
9649
|
+
target: adapter.skillsDir(),
|
|
9650
|
+
source: "agent-default"
|
|
9651
|
+
};
|
|
9652
|
+
}
|
|
9653
|
+
const candidates = listSkillTargetCandidates();
|
|
9654
|
+
const detected = candidates.filter((c) => c.detected);
|
|
9655
|
+
if (detected.length === 1) {
|
|
9656
|
+
const only = detected[0];
|
|
9657
|
+
return {
|
|
9658
|
+
agent: only.agent,
|
|
9659
|
+
agentLabel: only.label,
|
|
9660
|
+
target: only.target,
|
|
9661
|
+
source: "auto-detected",
|
|
9662
|
+
detectedTargets: detected
|
|
9663
|
+
};
|
|
9664
|
+
}
|
|
9665
|
+
if (detected.length === 0) {
|
|
9666
|
+
throw new YoooclawError(ErrorCode.NOT_FOUND, "未检测到可安装 Skill 的 Agent", {
|
|
9667
|
+
hint: "请显式指定 `--agent claude` / `--agent codex`,或使用 `--target <dir>` 指定任意 skills 目录",
|
|
9668
|
+
targets: candidates
|
|
9669
|
+
});
|
|
9670
|
+
}
|
|
9671
|
+
throw new YoooclawError(ErrorCode.CONFIRMATION_REQUIRED, "检测到多个 Agent,无法自动判断 Skill 应安装到哪里", {
|
|
9672
|
+
hint: "请显式指定 `--agent claude` / `--agent codex`,或使用 `--target <dir>`",
|
|
9673
|
+
targets: detected
|
|
9674
|
+
});
|
|
8972
9675
|
}
|
|
8973
9676
|
function candidateAnchorDirs() {
|
|
8974
9677
|
const out = [];
|
|
8975
9678
|
try {
|
|
8976
9679
|
const mainFile = require.main?.filename;
|
|
8977
9680
|
if (mainFile)
|
|
8978
|
-
out.push(
|
|
9681
|
+
out.push(import_node_path19.dirname(mainFile));
|
|
8979
9682
|
} catch {}
|
|
8980
9683
|
try {
|
|
8981
9684
|
const argv1 = process.argv[1];
|
|
8982
9685
|
if (argv1)
|
|
8983
|
-
out.push(
|
|
9686
|
+
out.push(import_node_path19.dirname(import_node_fs23.realpathSync(argv1)));
|
|
8984
9687
|
} catch {}
|
|
8985
9688
|
try {
|
|
8986
9689
|
const url = "file:///Users/cobb/github/openclaw-plugin/packages/cli/src/commands/skills.ts";
|
|
8987
9690
|
if (url)
|
|
8988
|
-
out.push(
|
|
9691
|
+
out.push(import_node_path19.dirname(import_node_url.fileURLToPath(url)));
|
|
8989
9692
|
} catch {}
|
|
8990
9693
|
return out;
|
|
8991
9694
|
}
|
|
8992
9695
|
function looksLikeSkillsDir(dir) {
|
|
8993
|
-
if (!
|
|
9696
|
+
if (!import_node_fs23.existsSync(dir))
|
|
8994
9697
|
return false;
|
|
8995
|
-
return
|
|
9698
|
+
return import_node_fs23.readdirSync(dir, { withFileTypes: true }).some((e) => e.isDirectory() && import_node_fs23.existsSync(import_node_path19.join(dir, e.name, "SKILL.md")));
|
|
8996
9699
|
}
|
|
8997
9700
|
function resolveBundledSkillsDir() {
|
|
8998
9701
|
const checked = [];
|
|
8999
9702
|
for (const anchor of candidateAnchorDirs()) {
|
|
9000
9703
|
let cur = anchor;
|
|
9001
9704
|
for (let i = 0;i < 6; i += 1) {
|
|
9002
|
-
const candidate =
|
|
9705
|
+
const candidate = import_node_path19.join(cur, "skills");
|
|
9003
9706
|
checked.push(candidate);
|
|
9004
9707
|
if (looksLikeSkillsDir(candidate))
|
|
9005
9708
|
return candidate;
|
|
9006
|
-
const parent =
|
|
9709
|
+
const parent = import_node_path19.dirname(cur);
|
|
9007
9710
|
if (parent === cur)
|
|
9008
9711
|
break;
|
|
9009
9712
|
cur = parent;
|
|
@@ -9024,9 +9727,9 @@ function parseSkillMeta(skillMd) {
|
|
|
9024
9727
|
return { name: pick("name"), description: pick("description") };
|
|
9025
9728
|
}
|
|
9026
9729
|
function listBundledSkills(skillsDir) {
|
|
9027
|
-
return
|
|
9028
|
-
const dir =
|
|
9029
|
-
const meta = parseSkillMeta(
|
|
9730
|
+
return import_node_fs23.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && import_node_fs23.existsSync(import_node_path19.join(skillsDir, e.name, "SKILL.md"))).map((e) => {
|
|
9731
|
+
const dir = import_node_path19.join(skillsDir, e.name);
|
|
9732
|
+
const meta = parseSkillMeta(import_node_fs23.readFileSync(import_node_path19.join(dir, "SKILL.md"), "utf-8"));
|
|
9030
9733
|
return {
|
|
9031
9734
|
name: e.name,
|
|
9032
9735
|
dir,
|
|
@@ -9047,25 +9750,33 @@ function skillsList(_ctx) {
|
|
|
9047
9750
|
title: s.title,
|
|
9048
9751
|
description: s.description
|
|
9049
9752
|
})),
|
|
9050
|
-
hint: "用 `yoooclaw skills
|
|
9753
|
+
hint: "用 `yoooclaw skills targets` 查看可安装目标,再用 `yoooclaw skills install --agent <agent>` 安装"
|
|
9754
|
+
};
|
|
9755
|
+
}
|
|
9756
|
+
function skillsTargets(_ctx) {
|
|
9757
|
+
const targets = listSkillTargetCandidates();
|
|
9758
|
+
return {
|
|
9759
|
+
ok: true,
|
|
9760
|
+
targets,
|
|
9761
|
+
hint: "裸 `yoooclaw skills install` 只会在检测到唯一 Agent 时自动安装;否则请显式传 `--agent` 或 `--target`"
|
|
9051
9762
|
};
|
|
9052
9763
|
}
|
|
9053
9764
|
function isSameSymlink(dest, source) {
|
|
9054
9765
|
try {
|
|
9055
|
-
if (!
|
|
9766
|
+
if (!import_node_fs23.lstatSync(dest).isSymbolicLink())
|
|
9056
9767
|
return false;
|
|
9057
|
-
return
|
|
9768
|
+
return import_node_fs23.realpathSync(dest) === import_node_fs23.realpathSync(source);
|
|
9058
9769
|
} catch {
|
|
9059
9770
|
return false;
|
|
9060
9771
|
}
|
|
9061
9772
|
}
|
|
9062
9773
|
function linkOrCopy(source, dest, mode) {
|
|
9063
9774
|
if (mode === "copy") {
|
|
9064
|
-
|
|
9775
|
+
import_node_fs23.cpSync(source, dest, { recursive: true });
|
|
9065
9776
|
return;
|
|
9066
9777
|
}
|
|
9067
9778
|
try {
|
|
9068
|
-
|
|
9779
|
+
import_node_fs23.symlinkSync(source, dest, "dir");
|
|
9069
9780
|
} catch (err) {
|
|
9070
9781
|
const e = err;
|
|
9071
9782
|
if (e.code === "EPERM" || e.code === "EACCES") {
|
|
@@ -9075,16 +9786,16 @@ function linkOrCopy(source, dest, mode) {
|
|
|
9075
9786
|
}
|
|
9076
9787
|
}
|
|
9077
9788
|
function installOne(skill, target, mode, force) {
|
|
9078
|
-
const source =
|
|
9079
|
-
const dest =
|
|
9080
|
-
if (
|
|
9789
|
+
const source = import_node_fs23.realpathSync(skill.dir);
|
|
9790
|
+
const dest = import_node_path19.join(target, skill.name);
|
|
9791
|
+
if (import_node_fs23.existsSync(dest) || isSymlinkPresent(dest)) {
|
|
9081
9792
|
if (mode === "symlink" && isSameSymlink(dest, source)) {
|
|
9082
9793
|
return { name: skill.name, status: "installed", via: "symlink", dest };
|
|
9083
9794
|
}
|
|
9084
9795
|
if (!force) {
|
|
9085
9796
|
return { name: skill.name, status: "skipped", reason: "目标已存在,加 --force 覆盖", dest };
|
|
9086
9797
|
}
|
|
9087
|
-
|
|
9798
|
+
import_node_fs23.rmSync(dest, { recursive: true, force: true });
|
|
9088
9799
|
}
|
|
9089
9800
|
linkOrCopy(source, dest, mode);
|
|
9090
9801
|
return { name: skill.name, status: "installed", via: mode, dest };
|
|
@@ -9092,25 +9803,30 @@ function installOne(skill, target, mode, force) {
|
|
|
9092
9803
|
function skillsInstall(_ctx, _args, opts) {
|
|
9093
9804
|
const skillsDir = resolveBundledSkillsDir();
|
|
9094
9805
|
const skills = listBundledSkills(skillsDir);
|
|
9095
|
-
const
|
|
9806
|
+
const selection = resolveTargetSelection(opts);
|
|
9807
|
+
const target = selection.target;
|
|
9096
9808
|
const mode = opts.copy ? "copy" : "symlink";
|
|
9097
|
-
|
|
9809
|
+
import_node_fs23.mkdirSync(target, { recursive: true });
|
|
9098
9810
|
const results = skills.map((skill) => installOne(skill, target, mode, opts.force ?? false));
|
|
9099
9811
|
const installed = results.filter((r) => r.status === "installed");
|
|
9100
9812
|
return {
|
|
9101
9813
|
ok: true,
|
|
9814
|
+
agent: selection.agent,
|
|
9815
|
+
agentLabel: selection.agentLabel,
|
|
9102
9816
|
target,
|
|
9817
|
+
targetSource: selection.source,
|
|
9103
9818
|
mode,
|
|
9104
9819
|
sourceDir: skillsDir,
|
|
9105
9820
|
installed: installed.map((r) => r.name),
|
|
9106
9821
|
skipped: results.filter((r) => r.status === "skipped"),
|
|
9107
9822
|
results,
|
|
9823
|
+
detectedTargets: selection.detectedTargets,
|
|
9108
9824
|
hint: installed.length > 0 ? '重启 agent 会话后即可被发现;试试说"看看最近的通知"触发 yoooclaw-notification-query' : "没有新安装的 Skill(已存在则加 --force 覆盖)"
|
|
9109
9825
|
};
|
|
9110
9826
|
}
|
|
9111
9827
|
function isSymlinkPresent(p) {
|
|
9112
9828
|
try {
|
|
9113
|
-
|
|
9829
|
+
import_node_fs23.lstatSync(p);
|
|
9114
9830
|
return true;
|
|
9115
9831
|
} catch {
|
|
9116
9832
|
return false;
|
|
@@ -9119,15 +9835,15 @@ function isSymlinkPresent(p) {
|
|
|
9119
9835
|
|
|
9120
9836
|
// src/commands/daemon.ts
|
|
9121
9837
|
var import_node_child_process2 = require("node:child_process");
|
|
9122
|
-
var
|
|
9838
|
+
var import_node_fs27 = require("node:fs");
|
|
9123
9839
|
var import_promises4 = require("node:timers/promises");
|
|
9124
9840
|
|
|
9125
9841
|
// src/daemon/main.ts
|
|
9126
9842
|
var import_node_http = require("node:http");
|
|
9127
9843
|
|
|
9128
9844
|
// src/daemon/logger.ts
|
|
9129
|
-
var
|
|
9130
|
-
var
|
|
9845
|
+
var import_node_fs24 = require("node:fs");
|
|
9846
|
+
var import_node_path20 = require("node:path");
|
|
9131
9847
|
var LEVEL_ORDER = {
|
|
9132
9848
|
error: 0,
|
|
9133
9849
|
warn: 1,
|
|
@@ -9138,9 +9854,9 @@ var LEVEL_ORDER = {
|
|
|
9138
9854
|
function isoLocal(d) {
|
|
9139
9855
|
const pad = (n, w = 2) => String(n).padStart(w, "0");
|
|
9140
9856
|
const off = -d.getTimezoneOffset();
|
|
9141
|
-
const
|
|
9857
|
+
const sign2 = off >= 0 ? "+" : "-";
|
|
9142
9858
|
const abs = Math.abs(off);
|
|
9143
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` + `T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}` + `${
|
|
9859
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` + `T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}` + `${sign2}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
|
|
9144
9860
|
}
|
|
9145
9861
|
function dateKey(d) {
|
|
9146
9862
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -9156,7 +9872,7 @@ class DaemonLogger {
|
|
|
9156
9872
|
this.logFile = logFile;
|
|
9157
9873
|
this.level = level;
|
|
9158
9874
|
this.alsoStderr = alsoStderr;
|
|
9159
|
-
ensureDir(
|
|
9875
|
+
ensureDir(import_node_path20.dirname(logFile));
|
|
9160
9876
|
this.currentDay = dateKey(new Date);
|
|
9161
9877
|
this.rotateIfNeeded(new Date);
|
|
9162
9878
|
}
|
|
@@ -9164,18 +9880,18 @@ class DaemonLogger {
|
|
|
9164
9880
|
return (LEVEL_ORDER[level] ?? 2) <= (LEVEL_ORDER[this.level] ?? 2);
|
|
9165
9881
|
}
|
|
9166
9882
|
rotateIfNeeded(now) {
|
|
9167
|
-
if (!
|
|
9883
|
+
if (!import_node_fs24.existsSync(this.logFile))
|
|
9168
9884
|
return;
|
|
9169
9885
|
let fileDay;
|
|
9170
9886
|
try {
|
|
9171
|
-
fileDay = dateKey(
|
|
9887
|
+
fileDay = dateKey(import_node_fs24.statSync(this.logFile).mtime);
|
|
9172
9888
|
} catch {
|
|
9173
9889
|
return;
|
|
9174
9890
|
}
|
|
9175
9891
|
const today2 = dateKey(now);
|
|
9176
9892
|
if (fileDay !== today2) {
|
|
9177
9893
|
try {
|
|
9178
|
-
|
|
9894
|
+
import_node_fs24.renameSync(this.logFile, `${this.logFile}.${fileDay}`);
|
|
9179
9895
|
} catch {}
|
|
9180
9896
|
}
|
|
9181
9897
|
}
|
|
@@ -9190,7 +9906,7 @@ class DaemonLogger {
|
|
|
9190
9906
|
const line = `${isoLocal(now)} [${level.toUpperCase()}] ${msg}
|
|
9191
9907
|
`;
|
|
9192
9908
|
try {
|
|
9193
|
-
|
|
9909
|
+
import_node_fs24.appendFileSync(this.logFile, line);
|
|
9194
9910
|
} catch {}
|
|
9195
9911
|
if (this.alsoStderr)
|
|
9196
9912
|
process.stderr.write(line);
|
|
@@ -9269,19 +9985,23 @@ class StandaloneRuntime {
|
|
|
9269
9985
|
}
|
|
9270
9986
|
|
|
9271
9987
|
// src/daemon/relay.ts
|
|
9272
|
-
var
|
|
9988
|
+
var import_node_path21 = require("node:path");
|
|
9273
9989
|
var DEFAULT_HEARTBEAT_SEC = 10;
|
|
9274
9990
|
var DEFAULT_RECONNECT_BACKOFF_MS = 2000;
|
|
9991
|
+
function relayCredential(apiKey) {
|
|
9992
|
+
const raw = apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey;
|
|
9993
|
+
return { query: { apiKey: raw } };
|
|
9994
|
+
}
|
|
9275
9995
|
function startRelayTunnel(opts) {
|
|
9276
9996
|
const { logger } = opts;
|
|
9277
|
-
const statusFilePath =
|
|
9997
|
+
const statusFilePath = import_node_path21.join(opts.stateDir, "state", "tunnel-status.json");
|
|
9278
9998
|
let abortController = new AbortController;
|
|
9279
9999
|
let connected = false;
|
|
9280
10000
|
let reconnectAttempt = 0;
|
|
9281
10001
|
let lastDisconnectReason;
|
|
9282
10002
|
const client = new RelayClient({
|
|
9283
10003
|
tunnelUrl: opts.tunnelUrl,
|
|
9284
|
-
|
|
10004
|
+
credentialProvider: async () => relayCredential(opts.apiKey),
|
|
9285
10005
|
heartbeatSec: opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC,
|
|
9286
10006
|
reconnectBackoffMs: opts.reconnectBackoffMs ?? DEFAULT_RECONNECT_BACKOFF_MS,
|
|
9287
10007
|
statusFilePath,
|
|
@@ -9337,15 +10057,15 @@ function startRelayTunnel(opts) {
|
|
|
9337
10057
|
}
|
|
9338
10058
|
|
|
9339
10059
|
// src/image/channel.ts
|
|
9340
|
-
var
|
|
10060
|
+
var import_node_fs25 = require("node:fs");
|
|
9341
10061
|
var import_node_stream = require("node:stream");
|
|
9342
|
-
var
|
|
10062
|
+
var import_node_path22 = require("node:path");
|
|
9343
10063
|
function readAll(paths) {
|
|
9344
10064
|
const p = imagesIndexPath(paths);
|
|
9345
|
-
if (!
|
|
10065
|
+
if (!import_node_fs25.existsSync(p))
|
|
9346
10066
|
return [];
|
|
9347
10067
|
try {
|
|
9348
|
-
const raw = JSON.parse(
|
|
10068
|
+
const raw = JSON.parse(import_node_fs25.readFileSync(p, "utf-8"));
|
|
9349
10069
|
return Array.isArray(raw?.images) ? raw.images : [];
|
|
9350
10070
|
} catch {
|
|
9351
10071
|
return [];
|
|
@@ -9400,10 +10120,10 @@ function ingestImage(paths, payload, opts) {
|
|
|
9400
10120
|
return { ok: true, imageId, status: "syncing" };
|
|
9401
10121
|
}
|
|
9402
10122
|
async function downloadInBackground(paths, entry, opts) {
|
|
9403
|
-
const filesDir =
|
|
10123
|
+
const filesDir = import_node_path22.join(paths.images, "files");
|
|
9404
10124
|
ensureDir(filesDir);
|
|
9405
10125
|
const relative = `files/${entry.imageId}.${extFromMime(entry.metadata.mime_type)}`;
|
|
9406
|
-
const dest =
|
|
10126
|
+
const dest = import_node_path22.join(paths.images, relative);
|
|
9407
10127
|
try {
|
|
9408
10128
|
const res = await fetch(entry.metadata.oss_image_url);
|
|
9409
10129
|
if (!res.ok || !res.body) {
|
|
@@ -9414,7 +10134,7 @@ async function downloadInBackground(paths, entry, opts) {
|
|
|
9414
10134
|
throw new Error(`图片超过上限 ${opts.maxBytes} 字节`);
|
|
9415
10135
|
}
|
|
9416
10136
|
let written = 0;
|
|
9417
|
-
const fileStream =
|
|
10137
|
+
const fileStream = import_node_fs25.createWriteStream(dest);
|
|
9418
10138
|
const reader = import_node_stream.Readable.fromWeb(res.body);
|
|
9419
10139
|
for await (const chunk of reader) {
|
|
9420
10140
|
written += chunk.length;
|
|
@@ -9446,17 +10166,17 @@ async function downloadInBackground(paths, entry, opts) {
|
|
|
9446
10166
|
}
|
|
9447
10167
|
|
|
9448
10168
|
// src/monitor/store.ts
|
|
9449
|
-
var
|
|
9450
|
-
var
|
|
10169
|
+
var import_node_fs26 = require("node:fs");
|
|
10170
|
+
var import_node_path23 = require("node:path");
|
|
9451
10171
|
function storePath(paths) {
|
|
9452
|
-
return
|
|
10172
|
+
return import_node_path23.join(paths.state, "monitors.json");
|
|
9453
10173
|
}
|
|
9454
10174
|
function listMonitors(paths) {
|
|
9455
10175
|
const p = storePath(paths);
|
|
9456
|
-
if (!
|
|
10176
|
+
if (!import_node_fs26.existsSync(p))
|
|
9457
10177
|
return [];
|
|
9458
10178
|
try {
|
|
9459
|
-
const raw = JSON.parse(
|
|
10179
|
+
const raw = JSON.parse(import_node_fs26.readFileSync(p, "utf-8"));
|
|
9460
10180
|
return Array.isArray(raw?.monitors) ? raw.monitors : [];
|
|
9461
10181
|
} catch {
|
|
9462
10182
|
return [];
|
|
@@ -9509,6 +10229,104 @@ function setMonitorEnabled(paths, name, enabled) {
|
|
|
9509
10229
|
return true;
|
|
9510
10230
|
}
|
|
9511
10231
|
|
|
10232
|
+
// src/daemon/light-send.ts
|
|
10233
|
+
async function performLightSend(input, deps) {
|
|
10234
|
+
let resolved;
|
|
10235
|
+
try {
|
|
10236
|
+
resolved = resolveSource(input, deps.registry);
|
|
10237
|
+
} catch (err) {
|
|
10238
|
+
return errorOutcome(400, "INVALID_PARAMS", err.message);
|
|
10239
|
+
}
|
|
10240
|
+
const overrideRepeat = input.repeat !== undefined || input.repeat_times !== undefined;
|
|
10241
|
+
let repeatTimes = resolved.repeat_times;
|
|
10242
|
+
if (overrideRepeat) {
|
|
10243
|
+
try {
|
|
10244
|
+
repeatTimes = normalizeRepeatTimes({
|
|
10245
|
+
repeat: input.repeat,
|
|
10246
|
+
repeat_times: input.repeat_times
|
|
10247
|
+
});
|
|
10248
|
+
} catch (err) {
|
|
10249
|
+
return errorOutcome(400, "VALIDATION_FAILED", err.message);
|
|
10250
|
+
}
|
|
10251
|
+
}
|
|
10252
|
+
try {
|
|
10253
|
+
assertAncsRepeatTimes(repeatTimes);
|
|
10254
|
+
} catch (err) {
|
|
10255
|
+
return errorOutcome(400, "VALIDATION_FAILED", err.message);
|
|
10256
|
+
}
|
|
10257
|
+
const apiKey = resolveApiKey();
|
|
10258
|
+
if (!apiKey.value) {
|
|
10259
|
+
return errorOutcome(401, "AUTH_REQUIRED", "未配置 api-key,运行 `yoooclaw auth set-api-key <ock_…>`");
|
|
10260
|
+
}
|
|
10261
|
+
deps.logger.info(`/light/send 解析完成 source=${resolved.source} reason=${resolved.reason} ` + `segments=${resolved.segments.length} repeat_times=${repeatTimes}`);
|
|
10262
|
+
const result = await sendLightEffect(apiKey.value, resolved.segments, deps.logger, { repeat_times: repeatTimes }, resolved.reason, resolved.title);
|
|
10263
|
+
if (!result.ok) {
|
|
10264
|
+
return errorOutcome(result.status ?? 502, "LIGHT_SEND_FAILED", result.error ?? "灯效投递失败");
|
|
10265
|
+
}
|
|
10266
|
+
return {
|
|
10267
|
+
ok: true,
|
|
10268
|
+
status: 200,
|
|
10269
|
+
body: {
|
|
10270
|
+
ok: true,
|
|
10271
|
+
accepted: true,
|
|
10272
|
+
delivered: true,
|
|
10273
|
+
source: resolved.source,
|
|
10274
|
+
bizUniqueId: result.bizUniqueId,
|
|
10275
|
+
segments: resolved.segments,
|
|
10276
|
+
repeat_times: repeatTimes,
|
|
10277
|
+
response: result.response
|
|
10278
|
+
}
|
|
10279
|
+
};
|
|
10280
|
+
}
|
|
10281
|
+
function resolveSource(input, registry) {
|
|
10282
|
+
if (input.rule) {
|
|
10283
|
+
const meta = registry.get(input.rule);
|
|
10284
|
+
if (!meta)
|
|
10285
|
+
throw new Error(`规则不存在:${input.rule}`);
|
|
10286
|
+
return {
|
|
10287
|
+
segments: meta.segments,
|
|
10288
|
+
repeat_times: meta.repeat_times,
|
|
10289
|
+
source: "rule",
|
|
10290
|
+
reason: `rule:${meta.name}`,
|
|
10291
|
+
title: meta.title
|
|
10292
|
+
};
|
|
10293
|
+
}
|
|
10294
|
+
if (input.preset) {
|
|
10295
|
+
const preset = LIGHT_PRESETS_V1.presets.find((p) => p.presetId === input.preset);
|
|
10296
|
+
if (!preset) {
|
|
10297
|
+
const available = LIGHT_PRESETS_V1.presets.map((p) => p.presetId).join(", ");
|
|
10298
|
+
throw new Error(`未知 preset:${input.preset}(可用:${available})`);
|
|
10299
|
+
}
|
|
10300
|
+
return {
|
|
10301
|
+
segments: preset.segments,
|
|
10302
|
+
repeat_times: preset.repeat_times,
|
|
10303
|
+
source: "preset",
|
|
10304
|
+
reason: `preset:${preset.presetId}`,
|
|
10305
|
+
title: preset.displayName
|
|
10306
|
+
};
|
|
10307
|
+
}
|
|
10308
|
+
if (input.segments !== undefined) {
|
|
10309
|
+
const validation = validateSegments(input.segments);
|
|
10310
|
+
if (!validation.valid) {
|
|
10311
|
+
throw new Error(JSON.stringify(validation.errors));
|
|
10312
|
+
}
|
|
10313
|
+
return {
|
|
10314
|
+
segments: validation.segments,
|
|
10315
|
+
repeat_times: 1,
|
|
10316
|
+
source: "segments",
|
|
10317
|
+
reason: "manual"
|
|
10318
|
+
};
|
|
10319
|
+
}
|
|
10320
|
+
throw new Error("需要 segments / preset / rule 之一");
|
|
10321
|
+
}
|
|
10322
|
+
function errorOutcome(status, code, message) {
|
|
10323
|
+
return {
|
|
10324
|
+
ok: false,
|
|
10325
|
+
status,
|
|
10326
|
+
body: { ok: false, error: { code, message } }
|
|
10327
|
+
};
|
|
10328
|
+
}
|
|
10329
|
+
|
|
9512
10330
|
// src/daemon/main.ts
|
|
9513
10331
|
var PROTOCOL_VERSION = 1;
|
|
9514
10332
|
var CAPABILITIES = ["notifications", "recordings", "images", "lightrules"];
|
|
@@ -9672,7 +10490,11 @@ async function runDaemonForeground(ctx, opts) {
|
|
|
9672
10490
|
const body = await parseJson(req, res);
|
|
9673
10491
|
if (body === undefined)
|
|
9674
10492
|
return;
|
|
9675
|
-
await
|
|
10493
|
+
const outcome = await performLightSend(body, {
|
|
10494
|
+
logger,
|
|
10495
|
+
registry: lightRuleRegistry
|
|
10496
|
+
});
|
|
10497
|
+
sendJson(res, outcome.status, outcome.body);
|
|
9676
10498
|
return;
|
|
9677
10499
|
}
|
|
9678
10500
|
if (path7 === "/images" && method === "POST") {
|
|
@@ -9741,27 +10563,6 @@ async function runDaemonForeground(ctx, opts) {
|
|
|
9741
10563
|
return;
|
|
9742
10564
|
}
|
|
9743
10565
|
}
|
|
9744
|
-
async function handleLightSend(body, res) {
|
|
9745
|
-
const input = body;
|
|
9746
|
-
if (!input.segments && !input.preset) {
|
|
9747
|
-
sendJson(res, 400, { ok: false, error: { code: "INVALID_PARAMS", message: "需要 --segments 或 --preset" } });
|
|
9748
|
-
return;
|
|
9749
|
-
}
|
|
9750
|
-
if (input.segments) {
|
|
9751
|
-
const validation = validateSegments(input.segments);
|
|
9752
|
-
if (!validation.valid) {
|
|
9753
|
-
sendJson(res, 400, { ok: false, error: { code: "VALIDATION_FAILED", message: JSON.stringify(validation.errors) } });
|
|
9754
|
-
return;
|
|
9755
|
-
}
|
|
9756
|
-
}
|
|
9757
|
-
logger.info(`/light/send 已接收(preset=${input.preset ?? "-"});standalone 无连接设备,未实际投递`);
|
|
9758
|
-
sendJson(res, 200, {
|
|
9759
|
-
ok: true,
|
|
9760
|
-
accepted: true,
|
|
9761
|
-
delivered: false,
|
|
9762
|
-
reason: "standalone daemon 暂无连接的灯效设备会话(需手机端在线 / relay)"
|
|
9763
|
-
});
|
|
9764
|
-
}
|
|
9765
10566
|
let serverRef = null;
|
|
9766
10567
|
let relayHandle = null;
|
|
9767
10568
|
let shuttingDown = false;
|
|
@@ -9956,9 +10757,9 @@ async function daemonStatus(ctx) {
|
|
|
9956
10757
|
return res.body;
|
|
9957
10758
|
}
|
|
9958
10759
|
function tailLines(file, n) {
|
|
9959
|
-
if (!
|
|
10760
|
+
if (!import_node_fs27.existsSync(file))
|
|
9960
10761
|
return [];
|
|
9961
|
-
const content =
|
|
10762
|
+
const content = import_node_fs27.readFileSync(file, "utf-8").split(`
|
|
9962
10763
|
`).filter(Boolean);
|
|
9963
10764
|
return content.slice(-n);
|
|
9964
10765
|
}
|
|
@@ -9976,14 +10777,14 @@ async function daemonLogs(ctx, _args, opts) {
|
|
|
9976
10777
|
for (const l of lines)
|
|
9977
10778
|
process.stdout.write(l + `
|
|
9978
10779
|
`);
|
|
9979
|
-
let size =
|
|
10780
|
+
let size = import_node_fs27.existsSync(file) ? import_node_fs27.statSync(file).size : 0;
|
|
9980
10781
|
await new Promise(() => {
|
|
9981
|
-
|
|
9982
|
-
if (!
|
|
10782
|
+
import_node_fs27.watchFile(file, { interval: 500 }, () => {
|
|
10783
|
+
if (!import_node_fs27.existsSync(file))
|
|
9983
10784
|
return;
|
|
9984
|
-
const cur =
|
|
10785
|
+
const cur = import_node_fs27.statSync(file).size;
|
|
9985
10786
|
if (cur > size) {
|
|
9986
|
-
const chunk =
|
|
10787
|
+
const chunk = import_node_fs27.readFileSync(file, "utf-8").slice(-(cur - size));
|
|
9987
10788
|
size = cur;
|
|
9988
10789
|
process.stdout.write(chunk);
|
|
9989
10790
|
} else if (cur < size) {
|
|
@@ -9995,7 +10796,7 @@ async function daemonLogs(ctx, _args, opts) {
|
|
|
9995
10796
|
}
|
|
9996
10797
|
|
|
9997
10798
|
// src/commands/daemon-services.ts
|
|
9998
|
-
var
|
|
10799
|
+
var import_node_fs28 = require("node:fs");
|
|
9999
10800
|
function client(ctx) {
|
|
10000
10801
|
assertDaemonRunning(ctx.paths);
|
|
10001
10802
|
return new DaemonClient(ctx.paths);
|
|
@@ -10010,11 +10811,16 @@ function parseJsonArg(raw, label) {
|
|
|
10010
10811
|
}
|
|
10011
10812
|
}
|
|
10012
10813
|
async function lightSend(ctx, _args, opts) {
|
|
10814
|
+
if (!opts.segments && !opts.preset && !opts.rule) {
|
|
10815
|
+
throw new YoooclawError("YOOOCLAW_INVALID_ARGUMENT", "需要 --segments / --preset / --rule 之一");
|
|
10816
|
+
}
|
|
10013
10817
|
const body = {};
|
|
10014
10818
|
if (opts.segments)
|
|
10015
10819
|
body.segments = parseJsonArg(opts.segments, "--segments");
|
|
10016
10820
|
if (opts.preset)
|
|
10017
10821
|
body.preset = opts.preset;
|
|
10822
|
+
if (opts.rule)
|
|
10823
|
+
body.rule = opts.rule;
|
|
10018
10824
|
if (opts.repeat)
|
|
10019
10825
|
body.repeat = true;
|
|
10020
10826
|
if (opts.repeatTimes !== undefined)
|
|
@@ -10023,7 +10829,7 @@ async function lightSend(ctx, _args, opts) {
|
|
|
10023
10829
|
return res.body;
|
|
10024
10830
|
}
|
|
10025
10831
|
async function lightBlink(ctx) {
|
|
10026
|
-
const res = await client(ctx).post("/light/send", { preset: "
|
|
10832
|
+
const res = await client(ctx).post("/light/send", { preset: "red-strobe-3" });
|
|
10027
10833
|
return res.body;
|
|
10028
10834
|
}
|
|
10029
10835
|
async function listRules(c) {
|
|
@@ -10042,7 +10848,7 @@ async function lightruleShow(ctx, args) {
|
|
|
10042
10848
|
}
|
|
10043
10849
|
async function buildRuleParams(opts, name) {
|
|
10044
10850
|
if (opts.fromFile) {
|
|
10045
|
-
const raw = opts.fromFile === "-" ? await readStdin() :
|
|
10851
|
+
const raw = opts.fromFile === "-" ? await readStdin() : import_node_fs28.readFileSync(opts.fromFile, "utf-8");
|
|
10046
10852
|
return JSON.parse(raw);
|
|
10047
10853
|
}
|
|
10048
10854
|
const params = {};
|
|
@@ -10182,7 +10988,7 @@ async function apiRaw(ctx, args, opts) {
|
|
|
10182
10988
|
if (opts.data === "-")
|
|
10183
10989
|
raw = await readStdin();
|
|
10184
10990
|
else if (opts.data.startsWith("@"))
|
|
10185
|
-
raw =
|
|
10991
|
+
raw = import_node_fs28.readFileSync(opts.data.slice(1), "utf-8");
|
|
10186
10992
|
else
|
|
10187
10993
|
raw = opts.data;
|
|
10188
10994
|
try {
|
|
@@ -10241,6 +11047,7 @@ var HANDLERS = {
|
|
|
10241
11047
|
"migrate from-openclaw": migrateFromOpenclaw,
|
|
10242
11048
|
"update self": updateSelf,
|
|
10243
11049
|
"skills list": skillsList,
|
|
11050
|
+
"skills targets": skillsTargets,
|
|
10244
11051
|
"skills install": skillsInstall,
|
|
10245
11052
|
doctor,
|
|
10246
11053
|
"daemon start": daemonStart,
|
|
@@ -10352,5 +11159,5 @@ async function run(argv = process.argv) {
|
|
|
10352
11159
|
// src/bin.ts
|
|
10353
11160
|
run(process.argv);
|
|
10354
11161
|
|
|
10355
|
-
//# debugId=
|
|
11162
|
+
//# debugId=C3FED3BF0AFFC1F564756E2164756E21
|
|
10356
11163
|
//# sourceMappingURL=bin.cjs.map
|