@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/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(import_node_path10.default.dirname(filePath), mkdirOptions);
2948
- const parentDir = import_node_path10.default.dirname(filePath);
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 import_node_path10, import_promises3, import_node_crypto5;
2999
+ var import_node_path9, import_promises3, import_node_crypto5;
3000
3000
  var init_json_files_DMrq2IfK = __esm(() => {
3001
- import_node_path10 = __toESM(require("node:path"));
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 ? import_node_path11.default.resolve(raw) : undefined;
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) ?? import_node_path11.default.resolve(process.cwd());
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 import_node_path11.default.resolve(expanded);
3069
+ return import_node_path10.default.resolve(expanded);
3070
3070
  }
3071
- return import_node_path11.default.resolve(trimmed);
3071
+ return import_node_path10.default.resolve(trimmed);
3072
3072
  }
3073
- var import_node_path11, import_node_os4;
3073
+ var import_node_path10, import_node_os4;
3074
3074
  var init_home_dir_BnP38vVl = __esm(() => {
3075
- import_node_path11 = __toESM(require("node:path"));
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) => import_node_path12.default.join(homedir3(), 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 import_node_path12.default.join(homedir3(), NEW_STATE_DIRNAME);
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 (import_node_fs14.default.existsSync(newDir))
3104
+ if (import_node_fs15.default.existsSync(newDir))
3105
3105
  return newDir;
3106
3106
  const existingLegacy = legacyDirs.find((dir) => {
3107
3107
  try {
3108
- return import_node_fs14.default.existsSync(dir);
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 import_node_path12.default.join(stateDir, CONFIG_FILENAME);
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 import_node_fs14.default.existsSync(candidate);
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(import_node_path12.default.join(resolved, CONFIG_FILENAME));
3153
- candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => import_node_path12.default.join(resolved, 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(import_node_path12.default.join(dir, CONFIG_FILENAME));
3158
- candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => import_node_path12.default.join(dir, 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 import_node_fs14, import_node_path12, import_node_os5, isNixMode, LEGACY_STATE_DIRNAMES, NEW_STATE_DIRNAME = ".openclaw", CONFIG_FILENAME = "openclaw.json", LEGACY_CONFIG_FILENAMES, STATE_DIR, CONFIG_PATH;
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
- import_node_fs14 = __toESM(require("node:fs"));
3166
- import_node_path12 = __toESM(require("node:path"));
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 = import_node_path13.default.join(root, subdir);
3218
+ const dir = import_node_path12.default.join(root, subdir);
3219
3219
  return {
3220
3220
  dir,
3221
- pendingPath: import_node_path13.default.join(dir, "pending.json"),
3222
- pairedPath: import_node_path13.default.join(dir, "paired.json")
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 import_node_path13, 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.";
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
- import_node_path13 = __toESM(require("node:path"));
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 import_node_path14.default.join(resolvePairingPaths(baseDir, "devices").dir, "bootstrap.json");
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 import_node_path14, PAIRING_SETUP_BOOTSTRAP_PROFILE, DEVICE_BOOTSTRAP_TOKEN_TTL_MS, withLock2;
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
- import_node_path14 = __toESM(require("node:path"));
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: "预设名,如 red-blink" },
3813
- { flags: "--repeat", summary: "无限循环播放" },
3814
- { flags: "--repeat-times <n>", summary: "整条组合重复次数(0=无限)" }
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 目录(默认 ~/.claude/skills,软链)",
3966
+ summary: "把内置 Skill 安装到 agent skills 目录(默认自动探测,软链)",
3965
3967
  options: [
3966
- { flags: "--target <dir>", summary: "安装目标目录", default: "~/.claude/skills" },
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.3".trim();
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 import_node_fs15 = require("node:fs");
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/tunnel/relay-client.ts
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
- import_node_fs11.mkdirSync(import_node_path7.dirname(this.opts.statusFilePath), { recursive: true });
6789
- import_node_fs11.writeFileSync(this.opts.statusFilePath, JSON.stringify(info, null, 2));
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
- const rawApiKey = this.opts.apiKey.startsWith("Bearer ") ? this.opts.apiKey.slice("Bearer ".length) : this.opts.apiKey;
6914
- this.opts.logger.info(`Relay tunnel: connecting to ${redactUrlSecrets(this.opts.tunnelUrl)} (attempt=${this.reconnectAttempt}, heartbeat=${this.opts.heartbeatSec}s, apiKey=${maskSecret(rawApiKey)})`);
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
- if (!wsUrl.searchParams.get("apiKey")) {
6926
- wsUrl.searchParams.set("apiKey", rawApiKey);
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 import_node_crypto4 = __toESM(require("node:crypto"));
7248
- var import_node_fs13 = __toESM(require("node:fs"));
7249
- var import_node_path9 = __toESM(require("node:path"));
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 = import_node_crypto4.default.createPublicKey(publicKeyPem).export({
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 import_node_crypto4.default.createHash("sha256").update(raw).digest("hex");
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 = import_node_crypto4.default.createPrivateKey(privateKeyPem);
7272
- return base64UrlEncode(import_node_crypto4.default.sign(null, Buffer.from(payload, "utf8"), key));
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
- import_node_fs13.default.mkdirSync(import_node_path9.default.dirname(filePath), { recursive: true });
7878
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
7294
7879
  }
7295
7880
  function resolveIdentityPath(stateDir) {
7296
- return import_node_path9.default.join(stateDir, "identity", "device.json");
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 import_node_path9.default.join(stateDir, "identity", "device-auth.json");
7897
+ return path.join(stateDir, "identity", "device-auth.json");
7313
7898
  }
7314
7899
  function readDeviceAuthStore(filePath) {
7315
7900
  try {
7316
- if (!import_node_fs13.default.existsSync(filePath))
7901
+ if (!fs.existsSync(filePath))
7317
7902
  return null;
7318
- const raw = import_node_fs13.default.readFileSync(filePath, "utf8");
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
- import_node_fs13.default.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}
7916
+ fs.writeFileSync(filePath, `${JSON.stringify(store, null, 2)}
7332
7917
  `, {
7333
7918
  mode: 384
7334
7919
  });
7335
7920
  try {
7336
- import_node_fs13.default.chmodSync(filePath, 384);
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 (import_node_fs13.default.existsSync(filePath)) {
7387
- const raw = import_node_fs13.default.readFileSync(filePath, "utf8");
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 } = import_node_crypto4.default.generateKeyPairSync("ed25519");
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
- import_node_fs13.default.mkdirSync(import_node_path9.default.dirname(filePath), { recursive: true });
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
- import_node_fs13.default.writeFileSync(filePath, `${JSON.stringify(stored, null, 2)}
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 (!import_node_fs15.existsSync(paths.notifications))
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 sign = min >= 0 ? "+" : "-";
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 `${sign}${hh}:${mm}`;
8946
+ return `${sign2}${hh}:${mm}`;
8358
8947
  }
8359
8948
 
8360
8949
  // src/notification/sync.ts
8361
- var import_node_fs16 = require("node:fs");
8362
- var import_node_path15 = require("node:path");
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 import_node_path15.join(dir, ".checkpoint.json");
8954
+ return import_node_path14.join(dir, ".checkpoint.json");
8366
8955
  }
8367
8956
  function readCheckpoint(dir) {
8368
8957
  const p = checkpointPath(dir);
8369
- if (!import_node_fs16.existsSync(p))
8958
+ if (!import_node_fs17.existsSync(p))
8370
8959
  return {};
8371
8960
  try {
8372
- return JSON.parse(import_node_fs16.readFileSync(p, "utf-8"));
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 (!import_node_fs16.existsSync(dir))
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 import_node_fs16.readdirSync(dir, { withFileTypes: true })) {
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 = import_node_path15.join(dir, `${dateKey}.json`);
8396
- if (!import_node_fs16.existsSync(filePath))
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(import_node_fs16.readFileSync(filePath, "utf-8"));
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 import_node_fs17 = require("node:fs");
8516
- var import_node_path16 = require("node:path");
9104
+ var import_node_fs18 = require("node:fs");
9105
+ var import_node_path15 = require("node:path");
8517
9106
  function readIndex(ctx) {
8518
- if (!import_node_fs17.existsSync(ctx.paths.recordings))
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 = import_node_path16.join(ctx.paths.recordings, "asr-config.json");
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 import_node_fs18 = require("node:fs");
8604
- var import_node_path17 = require("node:path");
9192
+ var import_node_fs19 = require("node:fs");
9193
+ var import_node_path16 = require("node:path");
8605
9194
  function imagesIndexPath(paths) {
8606
- return import_node_path17.join(paths.images, "index.json");
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 (!import_node_fs18.existsSync(indexPath))
9199
+ if (!import_node_fs19.existsSync(indexPath))
8611
9200
  return [];
8612
9201
  try {
8613
- const raw = JSON.parse(import_node_fs18.readFileSync(indexPath, "utf-8"));
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 import_node_path17.isAbsolute(relative) ? relative : import_node_path17.join(paths.images, relative);
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 import_node_fs19 = require("node:fs");
8675
- var import_node_path18 = require("node:path");
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 = import_node_path18.dirname(daemonLog);
8679
- const base = import_node_path18.basename(daemonLog);
8680
- if (!import_node_fs19.existsSync(dir))
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 = import_node_fs19.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_path18.join(dir, f));
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 (import_node_fs19.existsSync(daemonLog))
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 = import_node_fs19.readFileSync(file, "utf-8");
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 import_node_fs20 = require("node:fs");
9340
+ var import_node_fs21 = require("node:fs");
8752
9341
  var import_node_os6 = require("node:os");
8753
- var import_node_path19 = require("node:path");
9342
+ var import_node_path18 = require("node:path");
8754
9343
  function countFiles(dir) {
8755
- if (!import_node_fs20.existsSync(dir))
9344
+ if (!import_node_fs21.existsSync(dir))
8756
9345
  return 0;
8757
9346
  try {
8758
- return import_node_fs20.readdirSync(dir, { recursive: true }).length;
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 ?? import_node_path19.join(import_node_os6.homedir(), ".openclaw");
8765
- const pluginRoot = import_node_path19.join(sourceRoot, "plugins", "phone-notifications");
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: import_node_path19.join(pluginRoot, "notifications"), target: ctx.paths.notifications },
8768
- { name: "recordings", source: import_node_path19.join(pluginRoot, "recordings"), target: ctx.paths.recordings },
8769
- { name: "light-rules", source: import_node_path19.join(pluginRoot, "light-rules"), target: ctx.paths.lightRules },
8770
- { name: "images", source: import_node_path19.join(pluginRoot, "images"), target: ctx.paths.images }
8771
- ].map((p) => ({ ...p, exists: import_node_fs20.existsSync(p.source), fileCount: countFiles(p.source) }));
8772
- const srcCreds = readJsonFile(import_node_path19.join(sourceRoot, "credentials.json"));
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 = import_node_fs20.existsSync(ctx.paths.dir) && import_node_fs20.readdirSync(ctx.paths.dir).length > 0;
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
- import_node_fs20.cpSync(ctx.paths.dir, backupDir, { recursive: true });
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
- import_node_fs20.cpSync(plan.source, plan.target, { recursive: true });
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 import_node_fs21 = require("node:fs");
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 (!import_node_fs21.existsSync(dir)) {
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
- import_node_fs21.accessSync(dir, import_node_fs21.constants.R_OK | import_node_fs21.constants.W_OK);
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 = import_node_fs21.statSync(dir).mode & 511;
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 import_node_fs22 = require("node:fs");
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 import_node_path20 = require("node:path");
8970
- function defaultTarget() {
8971
- return import_node_path20.join(import_node_os7.homedir(), ".claude", "skills");
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(import_node_path20.dirname(mainFile));
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(import_node_path20.dirname(import_node_fs22.realpathSync(argv1)));
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(import_node_path20.dirname(import_node_url.fileURLToPath(url)));
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 (!import_node_fs22.existsSync(dir))
9696
+ if (!import_node_fs23.existsSync(dir))
8994
9697
  return false;
8995
- return import_node_fs22.readdirSync(dir, { withFileTypes: true }).some((e) => e.isDirectory() && import_node_fs22.existsSync(import_node_path20.join(dir, e.name, "SKILL.md")));
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 = import_node_path20.join(cur, "skills");
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 = import_node_path20.dirname(cur);
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 import_node_fs22.readdirSync(skillsDir, { withFileTypes: true }).filter((e) => e.isDirectory() && import_node_fs22.existsSync(import_node_path20.join(skillsDir, e.name, "SKILL.md"))).map((e) => {
9028
- const dir = import_node_path20.join(skillsDir, e.name);
9029
- const meta = parseSkillMeta(import_node_fs22.readFileSync(import_node_path20.join(dir, "SKILL.md"), "utf-8"));
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 install` 安装到 agent skills 目录(默认 ~/.claude/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 (!import_node_fs22.lstatSync(dest).isSymbolicLink())
9766
+ if (!import_node_fs23.lstatSync(dest).isSymbolicLink())
9056
9767
  return false;
9057
- return import_node_fs22.realpathSync(dest) === import_node_fs22.realpathSync(source);
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
- import_node_fs22.cpSync(source, dest, { recursive: true });
9775
+ import_node_fs23.cpSync(source, dest, { recursive: true });
9065
9776
  return;
9066
9777
  }
9067
9778
  try {
9068
- import_node_fs22.symlinkSync(source, dest, "dir");
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 = import_node_fs22.realpathSync(skill.dir);
9079
- const dest = import_node_path20.join(target, skill.name);
9080
- if (import_node_fs22.existsSync(dest) || isSymlinkPresent(dest)) {
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
- import_node_fs22.rmSync(dest, { recursive: true, force: true });
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 target = opts.target?.trim() || defaultTarget();
9806
+ const selection = resolveTargetSelection(opts);
9807
+ const target = selection.target;
9096
9808
  const mode = opts.copy ? "copy" : "symlink";
9097
- import_node_fs22.mkdirSync(target, { recursive: true });
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
- import_node_fs22.lstatSync(p);
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 import_node_fs26 = require("node:fs");
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 import_node_fs23 = require("node:fs");
9130
- var import_node_path21 = require("node:path");
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 sign = off >= 0 ? "+" : "-";
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)}` + `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`;
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(import_node_path21.dirname(logFile));
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 (!import_node_fs23.existsSync(this.logFile))
9883
+ if (!import_node_fs24.existsSync(this.logFile))
9168
9884
  return;
9169
9885
  let fileDay;
9170
9886
  try {
9171
- fileDay = dateKey(import_node_fs23.statSync(this.logFile).mtime);
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
- import_node_fs23.renameSync(this.logFile, `${this.logFile}.${fileDay}`);
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
- import_node_fs23.appendFileSync(this.logFile, line);
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 import_node_path22 = require("node:path");
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 = import_node_path22.join(opts.stateDir, "state", "tunnel-status.json");
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
- apiKey: opts.apiKey,
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 import_node_fs24 = require("node:fs");
10060
+ var import_node_fs25 = require("node:fs");
9341
10061
  var import_node_stream = require("node:stream");
9342
- var import_node_path23 = require("node:path");
10062
+ var import_node_path22 = require("node:path");
9343
10063
  function readAll(paths) {
9344
10064
  const p = imagesIndexPath(paths);
9345
- if (!import_node_fs24.existsSync(p))
10065
+ if (!import_node_fs25.existsSync(p))
9346
10066
  return [];
9347
10067
  try {
9348
- const raw = JSON.parse(import_node_fs24.readFileSync(p, "utf-8"));
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 = import_node_path23.join(paths.images, "files");
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 = import_node_path23.join(paths.images, relative);
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 = import_node_fs24.createWriteStream(dest);
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 import_node_fs25 = require("node:fs");
9450
- var import_node_path24 = require("node:path");
10169
+ var import_node_fs26 = require("node:fs");
10170
+ var import_node_path23 = require("node:path");
9451
10171
  function storePath(paths) {
9452
- return import_node_path24.join(paths.state, "monitors.json");
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 (!import_node_fs25.existsSync(p))
10176
+ if (!import_node_fs26.existsSync(p))
9457
10177
  return [];
9458
10178
  try {
9459
- const raw = JSON.parse(import_node_fs25.readFileSync(p, "utf-8"));
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 handleLightSend(body, res);
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 (!import_node_fs26.existsSync(file))
10760
+ if (!import_node_fs27.existsSync(file))
9960
10761
  return [];
9961
- const content = import_node_fs26.readFileSync(file, "utf-8").split(`
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 = import_node_fs26.existsSync(file) ? import_node_fs26.statSync(file).size : 0;
10780
+ let size = import_node_fs27.existsSync(file) ? import_node_fs27.statSync(file).size : 0;
9980
10781
  await new Promise(() => {
9981
- import_node_fs26.watchFile(file, { interval: 500 }, () => {
9982
- if (!import_node_fs26.existsSync(file))
10782
+ import_node_fs27.watchFile(file, { interval: 500 }, () => {
10783
+ if (!import_node_fs27.existsSync(file))
9983
10784
  return;
9984
- const cur = import_node_fs26.statSync(file).size;
10785
+ const cur = import_node_fs27.statSync(file).size;
9985
10786
  if (cur > size) {
9986
- const chunk = import_node_fs26.readFileSync(file, "utf-8").slice(-(cur - size));
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 import_node_fs27 = require("node:fs");
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: "blink" });
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() : import_node_fs27.readFileSync(opts.fromFile, "utf-8");
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 = import_node_fs27.readFileSync(opts.data.slice(1), "utf-8");
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=3D842115BFE90B5564756E2164756E21
11162
+ //# debugId=C3FED3BF0AFFC1F564756E2164756E21
10356
11163
  //# sourceMappingURL=bin.cjs.map