@yoooclaw/phone-notifications 1.5.2 → 1.6.0

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/index.js CHANGED
@@ -3647,8 +3647,8 @@ var require_websocket_server = __commonJS({
3647
3647
  });
3648
3648
 
3649
3649
  // src/index.ts
3650
- import { readFileSync as readFileSync11 } from "fs";
3651
- import { basename, dirname as dirname4 } from "path";
3650
+ import { readFileSync as readFileSync13 } from "fs";
3651
+ import { basename as basename2, dirname as dirname5 } from "path";
3652
3652
 
3653
3653
  // src/light/protocol.ts
3654
3654
  var MAX_LIGHT_SEGMENTS = 12;
@@ -3791,8 +3791,56 @@ function quantizeWindow(value) {
3791
3791
 
3792
3792
  // src/light/sender.ts
3793
3793
  import { randomUUID } from "crypto";
3794
+
3795
+ // src/env.ts
3796
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3797
+ import { join, dirname } from "path";
3798
+ var ENV_CONFIG = {
3799
+ development: {
3800
+ lightApiUrl: "https://openclaw-service-dev.yoootek.com/api/message/tob/sendMessage",
3801
+ relayTunnelUrl: "wss://openclaw-service-dev.yoootek.com/message/messages/ws/plugin"
3802
+ },
3803
+ production: {
3804
+ lightApiUrl: "https://openclaw-service.yoootek.com/api/message/tob/sendMessage",
3805
+ relayTunnelUrl: "wss://openclaw-service.yoootek.com/message/messages/ws/plugin"
3806
+ }
3807
+ };
3808
+ var VALID_ENVS = new Set(Object.keys(ENV_CONFIG));
3809
+ function envFilePath() {
3810
+ const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
3811
+ return join(home, ".openclaw", "env.json");
3812
+ }
3813
+ function loadEnvName() {
3814
+ const filePath = envFilePath();
3815
+ if (!existsSync(filePath)) return "development";
3816
+ try {
3817
+ const data = JSON.parse(readFileSync(filePath, "utf-8"));
3818
+ if (data.env && VALID_ENVS.has(data.env)) return data.env;
3819
+ } catch {
3820
+ }
3821
+ return "development";
3822
+ }
3823
+ function saveEnvName(env) {
3824
+ if (!VALID_ENVS.has(env)) {
3825
+ throw new Error(`\u65E0\u6548\u7684\u73AF\u5883\u540D\u79F0: ${env}\uFF0C\u53EF\u9009\u503C: ${[...VALID_ENVS].join(", ")}`);
3826
+ }
3827
+ const filePath = envFilePath();
3828
+ mkdirSync(dirname(filePath), { recursive: true, mode: 448 });
3829
+ writeFileSync(filePath, JSON.stringify({ env }, null, 2), {
3830
+ encoding: "utf-8",
3831
+ mode: 384
3832
+ });
3833
+ }
3834
+ function getEnvUrls(env) {
3835
+ return ENV_CONFIG[env ?? loadEnvName()];
3836
+ }
3837
+ function getAvailableEnvs() {
3838
+ return Object.keys(ENV_CONFIG);
3839
+ }
3840
+
3841
+ // src/light/sender.ts
3794
3842
  async function sendLightEffect(token, segments, logger, repeat) {
3795
- const apiUrl = "https://openclaw-service-dev.yoootek.com/api/message/tob/sendMessage";
3843
+ const apiUrl = getEnvUrls().lightApiUrl;
3796
3844
  const appKey = "7Q617S1G5WD274JI";
3797
3845
  const templateId = "1990771146010017788";
3798
3846
  logger?.info(
@@ -3836,27 +3884,203 @@ async function sendLightEffect(token, segments, logger, repeat) {
3836
3884
  return { ok: true, bizUniqueId, response: JSON.parse(resBody) };
3837
3885
  }
3838
3886
 
3887
+ // src/notification/app-name-map.ts
3888
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
3889
+ import { join as join3 } from "path";
3890
+
3891
+ // src/auth/credentials.ts
3892
+ import {
3893
+ existsSync as existsSync2,
3894
+ mkdirSync as mkdirSync2,
3895
+ readFileSync as readFileSync2,
3896
+ writeFileSync as writeFileSync2,
3897
+ watch
3898
+ } from "fs";
3899
+ import { join as join2, dirname as dirname2, basename } from "path";
3900
+ function credentialsPath() {
3901
+ const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
3902
+ return join2(home, ".openclaw", "credentials.json");
3903
+ }
3904
+ function readCredentials() {
3905
+ const path2 = credentialsPath();
3906
+ if (!existsSync2(path2)) return {};
3907
+ try {
3908
+ return JSON.parse(readFileSync2(path2, "utf-8"));
3909
+ } catch {
3910
+ return {};
3911
+ }
3912
+ }
3913
+ function writeCredentials(creds) {
3914
+ const path2 = credentialsPath();
3915
+ mkdirSync2(dirname2(path2), { recursive: true, mode: 448 });
3916
+ writeFileSync2(path2, JSON.stringify(creds, null, 2), {
3917
+ encoding: "utf-8",
3918
+ mode: 384
3919
+ });
3920
+ }
3921
+ function loadToken() {
3922
+ return readCredentials().token;
3923
+ }
3924
+ function requireToken() {
3925
+ const token = loadToken();
3926
+ if (!token) {
3927
+ throw new Error(
3928
+ "Token \u672A\u8BBE\u7F6E\uFF0C\u8BF7\u5148\u6267\u884C openclaw ntf auth set-token <token>\uFF08\u82E5 ntf \u547D\u4EE4\u51B2\u7A81\uFF0C\u53EF\u4F7F\u7528 openclaw phone-notifications auth set-token <token>\uFF09"
3929
+ );
3930
+ }
3931
+ return token;
3932
+ }
3933
+ function watchCredentials(onChange) {
3934
+ const path2 = credentialsPath();
3935
+ const dir = dirname2(path2);
3936
+ const filename = basename(path2);
3937
+ let debounceTimer = null;
3938
+ const delayMs = 200;
3939
+ const listener = (_event, changedName) => {
3940
+ if (!changedName || changedName !== filename && !changedName.endsWith(filename)) return;
3941
+ if (debounceTimer) clearTimeout(debounceTimer);
3942
+ debounceTimer = setTimeout(() => {
3943
+ debounceTimer = null;
3944
+ onChange();
3945
+ }, delayMs);
3946
+ };
3947
+ let watcher = null;
3948
+ try {
3949
+ watcher = watch(dir, { persistent: false }, listener);
3950
+ } catch {
3951
+ }
3952
+ return () => {
3953
+ if (debounceTimer) clearTimeout(debounceTimer);
3954
+ watcher?.close();
3955
+ };
3956
+ }
3957
+
3958
+ // src/notification/app-name-map.ts
3959
+ var PLUGIN_STATE_DIR = "phone-notifications";
3960
+ var CACHE_FILE = "app-name-map.json";
3961
+ var BUILTIN_APP_NAME_MAP_URL = "https://openclaw-service-dev.yoootek.com/api/application-config/app-package/config-all";
3962
+ var APP_NAME_MAP_URL = BUILTIN_APP_NAME_MAP_URL;
3963
+ var APP_NAME_MAP_REFRESH_HOURS = 12;
3964
+ function isRecordOfStrings(v) {
3965
+ if (v === null || typeof v !== "object") return false;
3966
+ for (const val of Object.values(v)) if (typeof val !== "string") return false;
3967
+ return true;
3968
+ }
3969
+ function isAppNameMapApiResponse(v) {
3970
+ if (v === null || typeof v !== "object") return false;
3971
+ const o = v;
3972
+ return Array.isArray(o.data) && o.data.every(
3973
+ (item) => item !== null && typeof item === "object" && typeof item.packageName === "string" && typeof item.appName === "string"
3974
+ );
3975
+ }
3976
+ function getCachePath(stateDir) {
3977
+ return join3(stateDir, "plugins", PLUGIN_STATE_DIR, CACHE_FILE);
3978
+ }
3979
+ function createAppNameMapProvider(opts) {
3980
+ const { stateDir, logger } = opts;
3981
+ const url = APP_NAME_MAP_URL;
3982
+ const refreshHours = APP_NAME_MAP_REFRESH_HOURS;
3983
+ const map = /* @__PURE__ */ new Map();
3984
+ let refreshTimer = null;
3985
+ let stopWatching = null;
3986
+ let inFlightFetch = null;
3987
+ function loadFromDisk() {
3988
+ const path2 = getCachePath(stateDir);
3989
+ if (!existsSync3(path2)) return;
3990
+ try {
3991
+ const raw = JSON.parse(readFileSync3(path2, "utf-8"));
3992
+ if (!isRecordOfStrings(raw)) return;
3993
+ map.clear();
3994
+ for (const [k, v] of Object.entries(raw)) map.set(k, v);
3995
+ } catch {
3996
+ }
3997
+ }
3998
+ async function fetchFromServer() {
3999
+ const token = loadToken();
4000
+ if (!token || !url) return;
4001
+ const auth = token.startsWith("Bearer ") ? token : `Bearer ${token}`;
4002
+ try {
4003
+ const res = await fetch(url, {
4004
+ method: "POST",
4005
+ headers: { "Content-Type": "application/json", Authorization: auth },
4006
+ body: JSON.stringify({
4007
+ platform: ""
4008
+ })
4009
+ });
4010
+ if (!res.ok) {
4011
+ return;
4012
+ }
4013
+ const body = await res.json();
4014
+ if (!isAppNameMapApiResponse(body) || !body.success || !body.data?.length) {
4015
+ return;
4016
+ }
4017
+ map.clear();
4018
+ for (const item of body.data) {
4019
+ if (item.packageName && item.appName) map.set(item.packageName, item.appName);
4020
+ }
4021
+ const dir = join3(stateDir, "plugins", PLUGIN_STATE_DIR);
4022
+ mkdirSync3(dir, { recursive: true });
4023
+ writeFileSync3(getCachePath(stateDir), JSON.stringify(Object.fromEntries(map), null, 2), "utf-8");
4024
+ } catch (e) {
4025
+ }
4026
+ }
4027
+ async function ensureOneFetch() {
4028
+ if (inFlightFetch) return inFlightFetch;
4029
+ inFlightFetch = fetchFromServer().finally(() => {
4030
+ inFlightFetch = null;
4031
+ });
4032
+ return inFlightFetch;
4033
+ }
4034
+ return {
4035
+ async resolveDisplayName(packageName) {
4036
+ if (map.has(packageName)) return map.get(packageName);
4037
+ return packageName;
4038
+ },
4039
+ async start() {
4040
+ loadFromDisk();
4041
+ await fetchFromServer();
4042
+ if (!url) return;
4043
+ if (refreshHours > 0) {
4044
+ const ms = refreshHours * 60 * 60 * 1e3;
4045
+ refreshTimer = setInterval(() => fetchFromServer().catch(() => {
4046
+ }), ms);
4047
+ }
4048
+ stopWatching = watchCredentials(() => fetchFromServer().catch(() => {
4049
+ }));
4050
+ },
4051
+ stop() {
4052
+ stopWatching?.();
4053
+ stopWatching = null;
4054
+ if (refreshTimer) {
4055
+ clearInterval(refreshTimer);
4056
+ refreshTimer = null;
4057
+ }
4058
+ map.clear();
4059
+ }
4060
+ };
4061
+ }
4062
+
3839
4063
  // src/notification/storage.ts
3840
4064
  import {
3841
4065
  accessSync,
3842
- mkdirSync,
4066
+ mkdirSync as mkdirSync4,
3843
4067
  appendFileSync,
3844
4068
  readdirSync,
3845
- readFileSync,
3846
- writeFileSync,
3847
- existsSync,
4069
+ readFileSync as readFileSync4,
4070
+ writeFileSync as writeFileSync4,
4071
+ existsSync as existsSync4,
3848
4072
  rmSync,
3849
4073
  constants
3850
4074
  } from "fs";
3851
- import { join } from "path";
4075
+ import { join as join4 } from "path";
3852
4076
  var NOTIFICATION_DIR_NAME = "notifications";
3853
4077
  var ID_INDEX_DIR_NAME = ".ids";
3854
4078
  function getStateFallbackNotificationDir(stateDir) {
3855
- return join(stateDir, "plugins", "phone-notifications", NOTIFICATION_DIR_NAME);
4079
+ return join4(stateDir, "plugins", "phone-notifications", NOTIFICATION_DIR_NAME);
3856
4080
  }
3857
4081
  function ensureWritableDirectory(dir) {
3858
4082
  try {
3859
- mkdirSync(dir, { recursive: true });
4083
+ mkdirSync4(dir, { recursive: true });
3860
4084
  accessSync(dir, constants.R_OK | constants.W_OK);
3861
4085
  return true;
3862
4086
  } catch {
@@ -3870,7 +4094,7 @@ function resolveNotificationStorageDir(ctx, logger) {
3870
4094
  return stateNotifDir;
3871
4095
  }
3872
4096
  if (ctx.workspaceDir) {
3873
- const workspaceDir = join(ctx.workspaceDir, NOTIFICATION_DIR_NAME);
4097
+ const workspaceDir = join4(ctx.workspaceDir, NOTIFICATION_DIR_NAME);
3874
4098
  if (ensureWritableDirectory(workspaceDir)) {
3875
4099
  logger.warn(
3876
4100
  `stateDir \u4E0D\u53EF\u7528\uFF0C\u901A\u77E5\u5DF2\u56DE\u9000\u5230 workspace \u8DEF\u5F84: ${workspaceDir}`
@@ -3881,53 +4105,58 @@ function resolveNotificationStorageDir(ctx, logger) {
3881
4105
  throw new Error(`\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528: ${stateNotifDir}`);
3882
4106
  }
3883
4107
  var NotificationStorage = class {
3884
- constructor(dir, config, logger) {
4108
+ constructor(dir, config, logger, resolveDisplayName) {
3885
4109
  this.config = config;
3886
4110
  this.logger = logger;
3887
4111
  this.dir = dir;
3888
- this.idIndexDir = join(dir, ID_INDEX_DIR_NAME);
4112
+ this.idIndexDir = join4(dir, ID_INDEX_DIR_NAME);
4113
+ this.resolveDisplayName = resolveDisplayName;
3889
4114
  }
3890
4115
  dir;
3891
4116
  idIndexDir;
3892
4117
  idCache = /* @__PURE__ */ new Map();
4118
+ resolveDisplayName;
3893
4119
  async init() {
3894
- mkdirSync(this.dir, { recursive: true });
3895
- mkdirSync(this.idIndexDir, { recursive: true });
4120
+ mkdirSync4(this.dir, { recursive: true });
4121
+ mkdirSync4(this.idIndexDir, { recursive: true });
3896
4122
  }
3897
4123
  async ingest(items) {
3898
4124
  for (const n of items) {
3899
- this.writeNotification(n);
4125
+ await this.writeNotification(n);
3900
4126
  }
3901
4127
  this.prune();
3902
4128
  }
3903
- writeNotification(n) {
4129
+ async writeNotification(n) {
3904
4130
  const ts = new Date(n.timestamp);
3905
4131
  if (Number.isNaN(ts.getTime())) {
3906
4132
  this.logger.warn(`\u5FFD\u7565\u975E\u6CD5 timestamp \u7684\u901A\u77E5: ${n.id}`);
3907
4133
  return;
3908
4134
  }
3909
4135
  const dateKey = this.formatDate(ts);
3910
- const filePath = join(this.dir, `${dateKey}.json`);
4136
+ const filePath = join4(this.dir, `${dateKey}.json`);
3911
4137
  const normalizedId = typeof n.id === "string" ? n.id.trim() : "";
3912
4138
  if (normalizedId && this.hasNotificationId(dateKey, normalizedId)) {
3913
4139
  return;
3914
4140
  }
4141
+ const appName = typeof n.app === "string" && n.app ? n.app : "Unknown";
4142
+ const appDisplayName = this.resolveDisplayName ? await this.resolveDisplayName(appName) : appName;
3915
4143
  const entry = {
3916
- appName: typeof n.app === "string" && n.app ? n.app : "Unknown",
4144
+ appName,
4145
+ appDisplayName,
3917
4146
  title: typeof n.title === "string" ? n.title : "",
3918
4147
  content: this.buildContent(n),
3919
4148
  timestamp: n.timestamp
3920
4149
  };
3921
4150
  let arr = [];
3922
- if (existsSync(filePath)) {
4151
+ if (existsSync4(filePath)) {
3923
4152
  try {
3924
- arr = JSON.parse(readFileSync(filePath, "utf-8"));
4153
+ arr = JSON.parse(readFileSync4(filePath, "utf-8"));
3925
4154
  } catch {
3926
4155
  arr = [];
3927
4156
  }
3928
4157
  }
3929
4158
  arr.push(entry);
3930
- writeFileSync(filePath, JSON.stringify(arr, null, 2), "utf-8");
4159
+ writeFileSync4(filePath, JSON.stringify(arr, null, 2), "utf-8");
3931
4160
  if (normalizedId) {
3932
4161
  this.recordNotificationId(dateKey, normalizedId);
3933
4162
  }
@@ -3953,7 +4182,7 @@ var NotificationStorage = class {
3953
4182
  return `${year}-${month}-${day}`;
3954
4183
  }
3955
4184
  getIdIndexPath(dateKey) {
3956
- return join(this.idIndexDir, `${dateKey}.ids`);
4185
+ return join4(this.idIndexDir, `${dateKey}.ids`);
3957
4186
  }
3958
4187
  getIdSet(dateKey) {
3959
4188
  const cached = this.idCache.get(dateKey);
@@ -3962,8 +4191,8 @@ var NotificationStorage = class {
3962
4191
  }
3963
4192
  const idPath = this.getIdIndexPath(dateKey);
3964
4193
  const ids = /* @__PURE__ */ new Set();
3965
- if (existsSync(idPath)) {
3966
- const lines = readFileSync(idPath, "utf-8").split(/\r?\n/);
4194
+ if (existsSync4(idPath)) {
4195
+ const lines = readFileSync4(idPath, "utf-8").split(/\r?\n/);
3967
4196
  for (const line of lines) {
3968
4197
  const id = line.trim();
3969
4198
  if (id) {
@@ -4002,10 +4231,10 @@ var NotificationStorage = class {
4002
4231
  if (entry.isFile()) {
4003
4232
  const match = dateFilePattern.exec(entry.name);
4004
4233
  if (match && match[1] < cutoffDate) {
4005
- rmSync(join(this.dir, entry.name), { force: true });
4234
+ rmSync(join4(this.dir, entry.name), { force: true });
4006
4235
  }
4007
4236
  } else if (entry.isDirectory() && dateDirPattern.test(entry.name) && entry.name < cutoffDate) {
4008
- rmSync(join(this.dir, entry.name), { recursive: true, force: true });
4237
+ rmSync(join4(this.dir, entry.name), { recursive: true, force: true });
4009
4238
  }
4010
4239
  }
4011
4240
  } catch {
@@ -4018,7 +4247,7 @@ var NotificationStorage = class {
4018
4247
  if (!entry.isFile()) continue;
4019
4248
  const match = /^(\d{4}-\d{2}-\d{2})\.ids$/.exec(entry.name);
4020
4249
  if (match && match[1] < cutoffDate) {
4021
- rmSync(join(this.idIndexDir, entry.name), { force: true });
4250
+ rmSync(join4(this.idIndexDir, entry.name), { force: true });
4022
4251
  this.idCache.delete(match[1]);
4023
4252
  }
4024
4253
  }
@@ -4031,24 +4260,24 @@ var NotificationStorage = class {
4031
4260
  };
4032
4261
 
4033
4262
  // src/cli/auth.ts
4034
- import { existsSync as existsSync4, rmSync as rmSync2 } from "fs";
4263
+ import { existsSync as existsSync6, rmSync as rmSync2 } from "fs";
4035
4264
 
4036
4265
  // src/cli/helpers.ts
4037
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
4038
- import { join as join2 } from "path";
4266
+ import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "fs";
4267
+ import { join as join5 } from "path";
4039
4268
  function resolveNotificationsDir(ctx) {
4040
4269
  if (ctx.stateDir) {
4041
- const dir = join2(
4270
+ const dir = join5(
4042
4271
  ctx.stateDir,
4043
4272
  "plugins",
4044
4273
  "phone-notifications",
4045
4274
  "notifications"
4046
4275
  );
4047
- if (existsSync2(dir)) return dir;
4276
+ if (existsSync5(dir)) return dir;
4048
4277
  }
4049
4278
  if (ctx.workspaceDir) {
4050
- const dir = join2(ctx.workspaceDir, "notifications");
4051
- if (existsSync2(dir)) return dir;
4279
+ const dir = join5(ctx.workspaceDir, "notifications");
4280
+ if (existsSync5(dir)) return dir;
4052
4281
  }
4053
4282
  return null;
4054
4283
  }
@@ -4063,10 +4292,10 @@ function listDateKeys(dir) {
4063
4292
  return keys.sort().reverse();
4064
4293
  }
4065
4294
  function readDateFile(dir, dateKey) {
4066
- const filePath = join2(dir, `${dateKey}.json`);
4067
- if (!existsSync2(filePath)) return [];
4295
+ const filePath = join5(dir, `${dateKey}.json`);
4296
+ if (!existsSync5(filePath)) return [];
4068
4297
  try {
4069
- return JSON.parse(readFileSync2(filePath, "utf-8"));
4298
+ return JSON.parse(readFileSync5(filePath, "utf-8"));
4070
4299
  } catch {
4071
4300
  return [];
4072
4301
  }
@@ -4096,48 +4325,6 @@ function exitError(code, message) {
4096
4325
  process.exit(1);
4097
4326
  }
4098
4327
 
4099
- // src/auth/credentials.ts
4100
- import {
4101
- existsSync as existsSync3,
4102
- mkdirSync as mkdirSync2,
4103
- readFileSync as readFileSync3,
4104
- writeFileSync as writeFileSync2
4105
- } from "fs";
4106
- import { join as join3, dirname } from "path";
4107
- function credentialsPath() {
4108
- const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
4109
- return join3(home, ".openclaw", "credentials.json");
4110
- }
4111
- function readCredentials() {
4112
- const path2 = credentialsPath();
4113
- if (!existsSync3(path2)) return {};
4114
- try {
4115
- return JSON.parse(readFileSync3(path2, "utf-8"));
4116
- } catch {
4117
- return {};
4118
- }
4119
- }
4120
- function writeCredentials(creds) {
4121
- const path2 = credentialsPath();
4122
- mkdirSync2(dirname(path2), { recursive: true, mode: 448 });
4123
- writeFileSync2(path2, JSON.stringify(creds, null, 2), {
4124
- encoding: "utf-8",
4125
- mode: 384
4126
- });
4127
- }
4128
- function loadToken() {
4129
- return readCredentials().token;
4130
- }
4131
- function requireToken() {
4132
- const token = loadToken();
4133
- if (!token) {
4134
- throw new Error(
4135
- "Token \u672A\u8BBE\u7F6E\uFF0C\u8BF7\u5148\u6267\u884C openclaw ntf auth set-token <token>\uFF08\u82E5 ntf \u547D\u4EE4\u51B2\u7A81\uFF0C\u53EF\u4F7F\u7528 openclaw phone-notifications auth set-token <token>\uFF09"
4136
- );
4137
- }
4138
- return token;
4139
- }
4140
-
4141
4328
  // src/cli/auth.ts
4142
4329
  function registerAuthCli(program) {
4143
4330
  const auth = program.command("auth").description("\u7528\u6237\u8BA4\u8BC1\u7BA1\u7406");
@@ -4164,7 +4351,7 @@ function registerAuthCli(program) {
4164
4351
  });
4165
4352
  auth.command("clear").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u8BA4\u8BC1\u4FE1\u606F").action(() => {
4166
4353
  const path2 = credentialsPath();
4167
- if (existsSync4(path2)) {
4354
+ if (existsSync6(path2)) {
4168
4355
  const creds = readCredentials();
4169
4356
  delete creds.token;
4170
4357
  if (Object.keys(creds).length === 0) {
@@ -4263,22 +4450,22 @@ function registerNtfStats(ntf, ctx) {
4263
4450
  }
4264
4451
 
4265
4452
  // src/cli/ntf-sync.ts
4266
- import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
4267
- import { join as join4 } from "path";
4453
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4454
+ import { join as join6 } from "path";
4268
4455
  function checkpointPath(dir) {
4269
- return join4(dir, ".checkpoint.json");
4456
+ return join6(dir, ".checkpoint.json");
4270
4457
  }
4271
4458
  function readCheckpoint(dir) {
4272
4459
  const p = checkpointPath(dir);
4273
- if (!existsSync5(p)) return {};
4460
+ if (!existsSync7(p)) return {};
4274
4461
  try {
4275
- return JSON.parse(readFileSync4(p, "utf-8"));
4462
+ return JSON.parse(readFileSync6(p, "utf-8"));
4276
4463
  } catch {
4277
4464
  return {};
4278
4465
  }
4279
4466
  }
4280
4467
  function writeCheckpoint(dir, data) {
4281
- writeFileSync3(checkpointPath(dir), JSON.stringify(data, null, 2), "utf-8");
4468
+ writeFileSync5(checkpointPath(dir), JSON.stringify(data, null, 2), "utf-8");
4282
4469
  }
4283
4470
  function registerNtfSync(ntf, ctx) {
4284
4471
  const sync = ntf.command("sync").description("\u540C\u6B65\u901A\u77E5\u5230\u8BB0\u5FC6\u7CFB\u7EDF");
@@ -4352,30 +4539,30 @@ function registerNtfSync(ntf, ctx) {
4352
4539
 
4353
4540
  // src/cli/ntf-monitor.ts
4354
4541
  import {
4355
- existsSync as existsSync6,
4356
- mkdirSync as mkdirSync3,
4357
- readFileSync as readFileSync5,
4358
- writeFileSync as writeFileSync4,
4542
+ existsSync as existsSync8,
4543
+ mkdirSync as mkdirSync5,
4544
+ readFileSync as readFileSync7,
4545
+ writeFileSync as writeFileSync6,
4359
4546
  rmSync as rmSync3,
4360
4547
  readdirSync as readdirSync3
4361
4548
  } from "fs";
4362
- import { join as join5 } from "path";
4549
+ import { join as join7 } from "path";
4363
4550
  function tasksDir(ctx) {
4364
4551
  const base = ctx.workspaceDir || ctx.stateDir;
4365
4552
  if (!base) throw new Error("workspaceDir and stateDir both unavailable");
4366
- return join5(base, "tasks");
4553
+ return join7(base, "tasks");
4367
4554
  }
4368
4555
  function readMeta(taskDir) {
4369
- const metaPath = join5(taskDir, "meta.json");
4370
- if (!existsSync6(metaPath)) return null;
4556
+ const metaPath = join7(taskDir, "meta.json");
4557
+ if (!existsSync8(metaPath)) return null;
4371
4558
  try {
4372
- return JSON.parse(readFileSync5(metaPath, "utf-8"));
4559
+ return JSON.parse(readFileSync7(metaPath, "utf-8"));
4373
4560
  } catch {
4374
4561
  return null;
4375
4562
  }
4376
4563
  }
4377
4564
  function writeMeta(taskDir, meta) {
4378
- writeFileSync4(join5(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
4565
+ writeFileSync6(join7(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
4379
4566
  }
4380
4567
  function generateFetchPy(name, matchRules) {
4381
4568
  const appName = matchRules.appName || "";
@@ -4452,27 +4639,27 @@ function registerNtfMonitor(ntf, ctx) {
4452
4639
  const monitor = ntf.command("monitor").description("\u901A\u77E5\u76D1\u63A7\u4EFB\u52A1\u7BA1\u7406");
4453
4640
  monitor.command("list").description("\u5217\u51FA\u6240\u6709\u76D1\u63A7\u4EFB\u52A1").action(() => {
4454
4641
  const dir = tasksDir(ctx);
4455
- if (!existsSync6(dir)) {
4642
+ if (!existsSync8(dir)) {
4456
4643
  output({ ok: true, tasks: [] });
4457
4644
  return;
4458
4645
  }
4459
4646
  const tasks = [];
4460
4647
  for (const entry of readdirSync3(dir, { withFileTypes: true })) {
4461
4648
  if (!entry.isDirectory()) continue;
4462
- const meta = readMeta(join5(dir, entry.name));
4649
+ const meta = readMeta(join7(dir, entry.name));
4463
4650
  if (meta) tasks.push(meta);
4464
4651
  }
4465
4652
  output({ ok: true, tasks });
4466
4653
  });
4467
4654
  monitor.command("show <name>").description("\u67E5\u770B\u76D1\u63A7\u4EFB\u52A1\u8BE6\u60C5").action((name) => {
4468
- const taskDir = join5(tasksDir(ctx), name);
4655
+ const taskDir = join7(tasksDir(ctx), name);
4469
4656
  const meta = readMeta(taskDir);
4470
4657
  if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
4471
- const checkpointPath2 = join5(taskDir, "checkpoint.json");
4658
+ const checkpointPath2 = join7(taskDir, "checkpoint.json");
4472
4659
  let checkpoint = {};
4473
- if (existsSync6(checkpointPath2)) {
4660
+ if (existsSync8(checkpointPath2)) {
4474
4661
  try {
4475
- checkpoint = JSON.parse(readFileSync5(checkpointPath2, "utf-8"));
4662
+ checkpoint = JSON.parse(readFileSync7(checkpointPath2, "utf-8"));
4476
4663
  } catch {
4477
4664
  }
4478
4665
  }
@@ -4487,8 +4674,8 @@ function registerNtfMonitor(ntf, ctx) {
4487
4674
  monitor.command("create <name>").description("\u521B\u5EFA\u76D1\u63A7\u4EFB\u52A1").requiredOption("--description <text>", "\u4EFB\u52A1\u63CF\u8FF0").requiredOption("--match-rules <json>", "\u5339\u914D\u89C4\u5219 JSON").requiredOption("--schedule <cron>", "cron \u8868\u8FBE\u5F0F").action(
4488
4675
  (name, opts) => {
4489
4676
  const dir = tasksDir(ctx);
4490
- const taskDir = join5(dir, name);
4491
- if (existsSync6(taskDir)) {
4677
+ const taskDir = join7(dir, name);
4678
+ if (existsSync8(taskDir)) {
4492
4679
  exitError("ALREADY_EXISTS", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u5DF2\u5B58\u5728`);
4493
4680
  }
4494
4681
  let matchRules;
@@ -4500,7 +4687,7 @@ function registerNtfMonitor(ntf, ctx) {
4500
4687
  "match-rules \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON"
4501
4688
  );
4502
4689
  }
4503
- mkdirSync3(taskDir, { recursive: true });
4690
+ mkdirSync5(taskDir, { recursive: true });
4504
4691
  const meta = {
4505
4692
  name,
4506
4693
  description: opts.description,
@@ -4510,13 +4697,13 @@ function registerNtfMonitor(ntf, ctx) {
4510
4697
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
4511
4698
  };
4512
4699
  writeMeta(taskDir, meta);
4513
- writeFileSync4(
4514
- join5(taskDir, "fetch.py"),
4700
+ writeFileSync6(
4701
+ join7(taskDir, "fetch.py"),
4515
4702
  generateFetchPy(name, matchRules),
4516
4703
  "utf-8"
4517
4704
  );
4518
- writeFileSync4(
4519
- join5(taskDir, "README.md"),
4705
+ writeFileSync6(
4706
+ join7(taskDir, "README.md"),
4520
4707
  generateReadme(name, opts.description),
4521
4708
  "utf-8"
4522
4709
  );
@@ -4544,8 +4731,8 @@ function registerNtfMonitor(ntf, ctx) {
4544
4731
  }
4545
4732
  );
4546
4733
  monitor.command("delete <name>").description("\u5220\u9664\u76D1\u63A7\u4EFB\u52A1").option("--yes", "\u8DF3\u8FC7\u786E\u8BA4").action((name, opts) => {
4547
- const taskDir = join5(tasksDir(ctx), name);
4548
- if (!existsSync6(taskDir)) {
4734
+ const taskDir = join7(tasksDir(ctx), name);
4735
+ if (!existsSync8(taskDir)) {
4549
4736
  exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
4550
4737
  }
4551
4738
  if (!opts.yes) {
@@ -4570,7 +4757,7 @@ function registerNtfMonitor(ntf, ctx) {
4570
4757
  });
4571
4758
  });
4572
4759
  monitor.command("enable <name>").description("\u542F\u7528\u76D1\u63A7\u4EFB\u52A1").action((name) => {
4573
- const taskDir = join5(tasksDir(ctx), name);
4760
+ const taskDir = join7(tasksDir(ctx), name);
4574
4761
  const meta = readMeta(taskDir);
4575
4762
  if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
4576
4763
  meta.enabled = true;
@@ -4578,7 +4765,7 @@ function registerNtfMonitor(ntf, ctx) {
4578
4765
  output({ ok: true, name, enabled: true });
4579
4766
  });
4580
4767
  monitor.command("disable <name>").description("\u6682\u505C\u76D1\u63A7\u4EFB\u52A1").action((name) => {
4581
- const taskDir = join5(tasksDir(ctx), name);
4768
+ const taskDir = join7(tasksDir(ctx), name);
4582
4769
  const meta = readMeta(taskDir);
4583
4770
  if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
4584
4771
  meta.enabled = false;
@@ -4588,8 +4775,8 @@ function registerNtfMonitor(ntf, ctx) {
4588
4775
  }
4589
4776
 
4590
4777
  // src/cli/light-rules.ts
4591
- import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
4592
- import { join as join6 } from "path";
4778
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
4779
+ import { join as join8 } from "path";
4593
4780
 
4594
4781
  // src/light/validators.ts
4595
4782
  var VALID_MODES = ["wave", "breath", "strobe", "steady", "wave_rainbow"];
@@ -4654,15 +4841,15 @@ var LIGHT_RULES_SECTION = "## \u706F\u6548\u89C4\u5219";
4654
4841
  function memoryPath(ctx) {
4655
4842
  const base = ctx.workspaceDir || ctx.stateDir;
4656
4843
  if (!base) throw new Error("workspaceDir and stateDir both unavailable");
4657
- return join6(base, "MEMORY.md");
4844
+ return join8(base, "MEMORY.md");
4658
4845
  }
4659
4846
  function readMemory(ctx) {
4660
4847
  const p = memoryPath(ctx);
4661
- if (!existsSync7(p)) return "";
4662
- return readFileSync6(p, "utf-8");
4848
+ if (!existsSync9(p)) return "";
4849
+ return readFileSync8(p, "utf-8");
4663
4850
  }
4664
4851
  function writeMemory(ctx, content) {
4665
- writeFileSync5(memoryPath(ctx), content, "utf-8");
4852
+ writeFileSync7(memoryPath(ctx), content, "utf-8");
4666
4853
  }
4667
4854
  function parseLightRules(content) {
4668
4855
  const rules = [];
@@ -4841,15 +5028,15 @@ function registerLightSend(light) {
4841
5028
  }
4842
5029
 
4843
5030
  // src/cli/tunnel-status.ts
4844
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
4845
- import { join as join7 } from "path";
4846
- var STATUS_REL_PATH = join7("plugins", "phone-notifications", "tunnel-status.json");
5031
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
5032
+ import { join as join9 } from "path";
5033
+ var STATUS_REL_PATH = join9("plugins", "phone-notifications", "tunnel-status.json");
4847
5034
  function readTunnelStatus(ctx) {
4848
5035
  if (!ctx.stateDir) return null;
4849
- const filePath = join7(ctx.stateDir, STATUS_REL_PATH);
4850
- if (!existsSync8(filePath)) return null;
5036
+ const filePath = join9(ctx.stateDir, STATUS_REL_PATH);
5037
+ if (!existsSync10(filePath)) return null;
4851
5038
  try {
4852
- return JSON.parse(readFileSync7(filePath, "utf-8"));
5039
+ return JSON.parse(readFileSync9(filePath, "utf-8"));
4853
5040
  } catch {
4854
5041
  return null;
4855
5042
  }
@@ -4870,7 +5057,7 @@ function formatMessage(status) {
4870
5057
  }
4871
5058
  function registerTunnelStatus(ntf, ctx) {
4872
5059
  ntf.command("tunnel-status").description("\u68C0\u67E5 Relay Tunnel \u96A7\u9053\u8FDE\u63A5\u72B6\u6001\uFF08\u8BFB\u53D6\u8FD0\u884C\u4E2D\u670D\u52A1\u7684\u72B6\u6001\u6587\u4EF6\uFF0C\u4E0D\u5F71\u54CD\u5DF2\u6709\u8FDE\u63A5\uFF09").action(async () => {
4873
- const tunnelUrl = "wss://openclaw-service-dev.yoootek.com/message/messages/ws/plugin";
5060
+ const tunnelUrl = getEnvUrls().relayTunnelUrl;
4874
5061
  const token = loadToken();
4875
5062
  if (!tunnelUrl) {
4876
5063
  exitError(
@@ -4917,17 +5104,17 @@ function registerNtfStoragePath(ntf, ctx) {
4917
5104
  }
4918
5105
 
4919
5106
  // src/cli/log-search.ts
4920
- import { existsSync as existsSync9, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "fs";
4921
- import { join as join8 } from "path";
5107
+ import { existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync4 } from "fs";
5108
+ import { join as join10 } from "path";
4922
5109
  function resolveLogsDir(ctx) {
4923
5110
  if (ctx.stateDir) {
4924
- const dir = join8(
5111
+ const dir = join10(
4925
5112
  ctx.stateDir,
4926
5113
  "plugins",
4927
5114
  "phone-notifications",
4928
5115
  "logs"
4929
5116
  );
4930
- if (existsSync9(dir)) return dir;
5117
+ if (existsSync11(dir)) return dir;
4931
5118
  }
4932
5119
  return null;
4933
5120
  }
@@ -4942,9 +5129,9 @@ function listLogDateKeys(dir) {
4942
5129
  return keys.sort().reverse();
4943
5130
  }
4944
5131
  function collectLogLines(dir, dateKey, keyword, limit, collected) {
4945
- const filePath = join8(dir, `${dateKey}.log`);
4946
- if (!existsSync9(filePath)) return;
4947
- const content = readFileSync8(filePath, "utf-8");
5132
+ const filePath = join10(dir, `${dateKey}.log`);
5133
+ if (!existsSync11(filePath)) return;
5134
+ const content = readFileSync10(filePath, "utf-8");
4948
5135
  const lowerKeyword = keyword?.toLowerCase();
4949
5136
  for (const line of content.split("\n")) {
4950
5137
  if (collected.length >= limit) return;
@@ -4975,13 +5162,47 @@ function registerLogSearch(ntf, ctx) {
4975
5162
  );
4976
5163
  }
4977
5164
 
5165
+ // src/cli/env.ts
5166
+ function registerEnvCli(ntf) {
5167
+ const env = ntf.command("env").description("\u73AF\u5883\u7BA1\u7406\uFF08\u5207\u6362 development / production\uFF09");
5168
+ env.command("show").description("\u663E\u793A\u5F53\u524D\u73AF\u5883\u53CA\u5BF9\u5E94\u7684\u63A5\u53E3\u5730\u5740").action(() => {
5169
+ const current = loadEnvName();
5170
+ const urls = getEnvUrls(current);
5171
+ output({
5172
+ ok: true,
5173
+ env: current,
5174
+ lightApiUrl: urls.lightApiUrl,
5175
+ relayTunnelUrl: urls.relayTunnelUrl,
5176
+ availableEnvs: getAvailableEnvs()
5177
+ });
5178
+ });
5179
+ env.command("switch <env>").description("\u5207\u6362\u73AF\u5883\uFF08development / production\uFF09").action((envName) => {
5180
+ const available = getAvailableEnvs();
5181
+ if (!available.includes(envName)) {
5182
+ exitError(
5183
+ "INVALID_ENV",
5184
+ `\u65E0\u6548\u7684\u73AF\u5883\u540D\u79F0: ${envName}\uFF0C\u53EF\u9009\u503C: ${available.join(", ")}`
5185
+ );
5186
+ }
5187
+ saveEnvName(envName);
5188
+ const urls = getEnvUrls(envName);
5189
+ output({
5190
+ ok: true,
5191
+ message: `\u5DF2\u5207\u6362\u5230 ${envName} \u73AF\u5883`,
5192
+ env: envName,
5193
+ lightApiUrl: urls.lightApiUrl,
5194
+ relayTunnelUrl: urls.relayTunnelUrl
5195
+ });
5196
+ });
5197
+ }
5198
+
4978
5199
  // src/version.ts
4979
- import { readFileSync as readFileSync9 } from "fs";
5200
+ import { readFileSync as readFileSync11 } from "fs";
4980
5201
  function readPluginVersion() {
4981
5202
  try {
4982
5203
  const packageJsonUrl = new URL("../package.json", import.meta.url);
4983
5204
  const packageJson = JSON.parse(
4984
- readFileSync9(packageJsonUrl, "utf-8")
5205
+ readFileSync11(packageJsonUrl, "utf-8")
4985
5206
  );
4986
5207
  return packageJson.version ?? "unknown";
4987
5208
  } catch {
@@ -5003,22 +5224,23 @@ function registerAllCli(program, ctx, rootCommandName = "ntf") {
5003
5224
  registerNtfStoragePath(ntf, ctx);
5004
5225
  registerLogSearch(ntf, ctx);
5005
5226
  registerTunnelStatus(ntf, ctx);
5227
+ registerEnvCli(ntf);
5006
5228
  }
5007
5229
 
5008
5230
  // src/tunnel/service.ts
5009
5231
  import {
5010
5232
  closeSync,
5011
- mkdirSync as mkdirSync5,
5233
+ mkdirSync as mkdirSync7,
5012
5234
  openSync,
5013
- readFileSync as readFileSync10,
5235
+ readFileSync as readFileSync12,
5014
5236
  unlinkSync,
5015
- writeFileSync as writeFileSync7
5237
+ writeFileSync as writeFileSync9
5016
5238
  } from "fs";
5017
- import { dirname as dirname3, join as join9 } from "path";
5239
+ import { dirname as dirname4, join as join11 } from "path";
5018
5240
 
5019
5241
  // src/tunnel/relay-client.ts
5020
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync4 } from "fs";
5021
- import { dirname as dirname2 } from "path";
5242
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
5243
+ import { dirname as dirname3 } from "path";
5022
5244
 
5023
5245
  // node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
5024
5246
  var import_stream = __toESM(require_stream(), 1);
@@ -5050,8 +5272,8 @@ var RelayClient = class {
5050
5272
  lastDisconnectReason
5051
5273
  };
5052
5274
  try {
5053
- mkdirSync4(dirname2(this.opts.statusFilePath), { recursive: true });
5054
- writeFileSync6(this.opts.statusFilePath, JSON.stringify(info, null, 2));
5275
+ mkdirSync6(dirname3(this.opts.statusFilePath), { recursive: true });
5276
+ writeFileSync8(this.opts.statusFilePath, JSON.stringify(info, null, 2));
5055
5277
  } catch {
5056
5278
  }
5057
5279
  }
@@ -5897,7 +6119,7 @@ function createTunnelService(opts) {
5897
6119
  }
5898
6120
  function readLockOwner(filePath) {
5899
6121
  try {
5900
- const parsed = JSON.parse(readFileSync10(filePath, "utf-8"));
6122
+ const parsed = JSON.parse(readFileSync12(filePath, "utf-8"));
5901
6123
  return typeof parsed.pid === "number" ? parsed.pid : null;
5902
6124
  } catch {
5903
6125
  return null;
@@ -5922,11 +6144,11 @@ function createTunnelService(opts) {
5922
6144
  }
5923
6145
  }
5924
6146
  function acquireLock(filePath) {
5925
- mkdirSync5(dirname3(filePath), { recursive: true });
6147
+ mkdirSync7(dirname4(filePath), { recursive: true });
5926
6148
  for (let attempt = 0; attempt < 2; attempt++) {
5927
6149
  try {
5928
6150
  const fd = openSync(filePath, "wx", 384);
5929
- writeFileSync7(
6151
+ writeFileSync9(
5930
6152
  fd,
5931
6153
  JSON.stringify({
5932
6154
  pid: process.pid,
@@ -5983,12 +6205,12 @@ function createTunnelService(opts) {
5983
6205
  return;
5984
6206
  }
5985
6207
  const { logger } = opts;
5986
- const baseStateDir = join9(ctx.stateDir, "plugins", "phone-notifications");
6208
+ const baseStateDir = join11(ctx.stateDir, "plugins", "phone-notifications");
5987
6209
  logger.info(
5988
6210
  `Relay tunnel: starting (pid=${process.pid}, url=${opts.tunnelUrl}, heartbeat=${opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC}s, backoff=${opts.reconnectBackoffMs ?? DEFAULT_RECONNECT_BACKOFF_MS}ms, gateway=${opts.gatewayBaseUrl}, hasGatewayToken=${!!opts.gatewayToken}, hasGatewayPwd=${!!opts.gatewayPassword})`
5989
6211
  );
5990
- const statusFilePath = join9(baseStateDir, "tunnel-status.json");
5991
- const lockPath = join9(baseStateDir, "relay-tunnel.lock");
6212
+ const statusFilePath = join11(baseStateDir, "tunnel-status.json");
6213
+ const lockPath = join11(baseStateDir, "relay-tunnel.lock");
5992
6214
  if (!acquireLock(lockPath)) {
5993
6215
  return;
5994
6216
  }
@@ -6036,13 +6258,13 @@ function createTunnelService(opts) {
6036
6258
  }
6037
6259
 
6038
6260
  // src/logger.ts
6039
- import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "fs";
6040
- import { join as join10 } from "path";
6261
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync8 } from "fs";
6262
+ import { join as join12 } from "path";
6041
6263
  var PluginFileLogger = class {
6042
6264
  constructor(upstream, stateDir) {
6043
6265
  this.upstream = upstream;
6044
- this.logsDir = join10(stateDir, "plugins", "phone-notifications", "logs");
6045
- mkdirSync6(this.logsDir, { recursive: true });
6266
+ this.logsDir = join12(stateDir, "plugins", "phone-notifications", "logs");
6267
+ mkdirSync8(this.logsDir, { recursive: true });
6046
6268
  }
6047
6269
  logsDir;
6048
6270
  info(msg) {
@@ -6064,7 +6286,7 @@ var PluginFileLogger = class {
6064
6286
  const line = `${time} [${level}] ${msg}
6065
6287
  `;
6066
6288
  try {
6067
- appendFileSync2(join10(this.logsDir, `${dateKey}.log`), line);
6289
+ appendFileSync2(join12(this.logsDir, `${dateKey}.log`), line);
6068
6290
  } catch {
6069
6291
  }
6070
6292
  }
@@ -6101,7 +6323,7 @@ function resolveLocalGatewayAuth(params) {
6101
6323
  const configPath = params.stateDir ? `${params.stateDir}/openclaw.json` : void 0;
6102
6324
  if (configPath) {
6103
6325
  try {
6104
- const configData = JSON.parse(readFileSync11(configPath, "utf-8"));
6326
+ const configData = JSON.parse(readFileSync13(configPath, "utf-8"));
6105
6327
  const rawGatewayAuthMode = trimToUndefined(configData?.gateway?.auth?.mode);
6106
6328
  if (rawGatewayAuthMode === "token" || rawGatewayAuthMode === "password") {
6107
6329
  configGatewayAuthMode = rawGatewayAuthMode;
@@ -6128,6 +6350,7 @@ var index_default = {
6128
6350
  register(api) {
6129
6351
  const config = api.pluginConfig ?? {};
6130
6352
  let storage = null;
6353
+ let appNameMapProvider = null;
6131
6354
  const ignoredApps = new Set(config.ignoredApps ?? []);
6132
6355
  const openclawDir = api.runtime.state.resolveStateDir();
6133
6356
  const logger = openclawDir ? new PluginFileLogger(api.logger, openclawDir) : api.logger;
@@ -6138,17 +6361,25 @@ var index_default = {
6138
6361
  id: "notification-storage",
6139
6362
  async start(ctx) {
6140
6363
  const storageDir = resolveNotificationStorageDir(ctx, logger);
6141
- storage = new NotificationStorage(storageDir, config, logger);
6364
+ appNameMapProvider = createAppNameMapProvider({
6365
+ stateDir: ctx.stateDir,
6366
+ logger
6367
+ });
6368
+ await appNameMapProvider.start();
6369
+ const resolveDisplayName = appNameMapProvider.resolveDisplayName.bind(appNameMapProvider);
6370
+ storage = new NotificationStorage(storageDir, config, logger, resolveDisplayName);
6142
6371
  await storage.init();
6143
6372
  logger.info(`\u901A\u77E5\u5B58\u50A8\u670D\u52A1\u5DF2\u542F\u52A8: ${storageDir}`);
6144
6373
  },
6145
6374
  async stop() {
6146
6375
  await storage?.close();
6147
6376
  storage = null;
6377
+ appNameMapProvider?.stop();
6378
+ appNameMapProvider = null;
6148
6379
  logger.info("\u901A\u77E5\u5B58\u50A8\u670D\u52A1\u5DF2\u505C\u6B62");
6149
6380
  }
6150
6381
  });
6151
- const tunnelUrl = "wss://openclaw-service-dev.yoootek.com/message/messages/ws/plugin";
6382
+ const tunnelUrl = getEnvUrls().relayTunnelUrl;
6152
6383
  if (tunnelUrl) {
6153
6384
  const gatewayPort = process.env.OPENCLAW_GATEWAY_PORT || "18789";
6154
6385
  const { gatewayAuthMode, gatewayToken, gatewayPassword } = resolveLocalGatewayAuth({
@@ -6373,8 +6604,8 @@ var index_default = {
6373
6604
  const inferOpenClawRootDir = (workspaceDir) => {
6374
6605
  if (openclawDir) return openclawDir;
6375
6606
  if (!workspaceDir) return void 0;
6376
- if (basename(workspaceDir) !== "workspace") return void 0;
6377
- return dirname4(workspaceDir);
6607
+ if (basename2(workspaceDir) !== "workspace") return void 0;
6608
+ return dirname5(workspaceDir);
6378
6609
  };
6379
6610
  api.registerCli(
6380
6611
  (ctx) => {