@yoooclaw/phone-notifications 1.5.2 → 1.6.1
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 +452 -185
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
3651
|
-
import { basename, dirname as
|
|
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,12 +3791,62 @@ function quantizeWindow(value) {
|
|
|
3791
3791
|
|
|
3792
3792
|
// src/light/sender.ts
|
|
3793
3793
|
import { randomUUID } from "crypto";
|
|
3794
|
-
|
|
3795
|
-
|
|
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
|
+
appNameMapUrl: "https://openclaw-service-dev.yoootek.com/api/application-config/app-package/config-all"
|
|
3803
|
+
},
|
|
3804
|
+
production: {
|
|
3805
|
+
lightApiUrl: "https://openclaw-service.yoootek.com/api/message/tob/sendMessage",
|
|
3806
|
+
relayTunnelUrl: "wss://openclaw-service.yoootek.com/message/messages/ws/plugin",
|
|
3807
|
+
appNameMapUrl: "https://openclaw-service.yoootek.com/api/application-config/app-package/config-all"
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
var VALID_ENVS = new Set(Object.keys(ENV_CONFIG));
|
|
3811
|
+
function envFilePath() {
|
|
3812
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
3813
|
+
return join(home, ".openclaw", "env.json");
|
|
3814
|
+
}
|
|
3815
|
+
function loadEnvName() {
|
|
3816
|
+
const filePath = envFilePath();
|
|
3817
|
+
if (!existsSync(filePath)) return "production";
|
|
3818
|
+
try {
|
|
3819
|
+
const data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
3820
|
+
if (data.env && VALID_ENVS.has(data.env)) return data.env;
|
|
3821
|
+
} catch {
|
|
3822
|
+
}
|
|
3823
|
+
return "production";
|
|
3824
|
+
}
|
|
3825
|
+
function saveEnvName(env) {
|
|
3826
|
+
if (!VALID_ENVS.has(env)) {
|
|
3827
|
+
throw new Error(`\u65E0\u6548\u7684\u73AF\u5883\u540D\u79F0: ${env}\uFF0C\u53EF\u9009\u503C: ${[...VALID_ENVS].join(", ")}`);
|
|
3828
|
+
}
|
|
3829
|
+
const filePath = envFilePath();
|
|
3830
|
+
mkdirSync(dirname(filePath), { recursive: true, mode: 448 });
|
|
3831
|
+
writeFileSync(filePath, JSON.stringify({ env }, null, 2), {
|
|
3832
|
+
encoding: "utf-8",
|
|
3833
|
+
mode: 384
|
|
3834
|
+
});
|
|
3835
|
+
}
|
|
3836
|
+
function getEnvUrls(env) {
|
|
3837
|
+
return ENV_CONFIG[env ?? loadEnvName()];
|
|
3838
|
+
}
|
|
3839
|
+
function getAvailableEnvs() {
|
|
3840
|
+
return Object.keys(ENV_CONFIG);
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
// src/light/sender.ts
|
|
3844
|
+
async function sendLightEffect(apiKey, segments, logger, repeat) {
|
|
3845
|
+
const apiUrl = getEnvUrls().lightApiUrl;
|
|
3796
3846
|
const appKey = "7Q617S1G5WD274JI";
|
|
3797
3847
|
const templateId = "1990771146010017788";
|
|
3798
3848
|
logger?.info(
|
|
3799
|
-
`Light sender: apiUrl=${apiUrl ?? "UNSET"}, appKey=${appKey ? appKey.substring(0, 8) + "\u2026" : "UNSET"}, templateId=${templateId ?? "UNSET"},
|
|
3849
|
+
`Light sender: apiUrl=${apiUrl ?? "UNSET"}, appKey=${appKey ? appKey.substring(0, 8) + "\u2026" : "UNSET"}, templateId=${templateId ?? "UNSET"}, apiKey=${apiKey ? apiKey.substring(0, 20) + "\u2026" : "EMPTY"}, segments=${JSON.stringify(segments)}`
|
|
3800
3850
|
);
|
|
3801
3851
|
if (!apiUrl || !appKey || !templateId) {
|
|
3802
3852
|
return {
|
|
@@ -3821,7 +3871,7 @@ async function sendLightEffect(token, segments, logger, repeat) {
|
|
|
3821
3871
|
method: "POST",
|
|
3822
3872
|
headers: {
|
|
3823
3873
|
"Content-Type": "application/json",
|
|
3824
|
-
|
|
3874
|
+
"X-Api-Key-Id": apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey
|
|
3825
3875
|
},
|
|
3826
3876
|
body: JSON.stringify(requestBody)
|
|
3827
3877
|
});
|
|
@@ -3836,27 +3886,222 @@ async function sendLightEffect(token, segments, logger, repeat) {
|
|
|
3836
3886
|
return { ok: true, bizUniqueId, response: JSON.parse(resBody) };
|
|
3837
3887
|
}
|
|
3838
3888
|
|
|
3889
|
+
// src/notification/app-name-map.ts
|
|
3890
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3891
|
+
import { join as join3 } from "path";
|
|
3892
|
+
|
|
3893
|
+
// src/auth/credentials.ts
|
|
3894
|
+
import {
|
|
3895
|
+
existsSync as existsSync2,
|
|
3896
|
+
mkdirSync as mkdirSync2,
|
|
3897
|
+
readFileSync as readFileSync2,
|
|
3898
|
+
writeFileSync as writeFileSync2,
|
|
3899
|
+
watch
|
|
3900
|
+
} from "fs";
|
|
3901
|
+
import { join as join2, dirname as dirname2, basename } from "path";
|
|
3902
|
+
function credentialsPath() {
|
|
3903
|
+
const home = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
|
3904
|
+
return join2(home, ".openclaw", "credentials.json");
|
|
3905
|
+
}
|
|
3906
|
+
function readCredentials() {
|
|
3907
|
+
const path2 = credentialsPath();
|
|
3908
|
+
if (!existsSync2(path2)) return {};
|
|
3909
|
+
try {
|
|
3910
|
+
return JSON.parse(readFileSync2(path2, "utf-8"));
|
|
3911
|
+
} catch {
|
|
3912
|
+
return {};
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
function writeCredentials(creds) {
|
|
3916
|
+
const path2 = credentialsPath();
|
|
3917
|
+
mkdirSync2(dirname2(path2), { recursive: true, mode: 448 });
|
|
3918
|
+
writeFileSync2(path2, JSON.stringify(creds, null, 2), {
|
|
3919
|
+
encoding: "utf-8",
|
|
3920
|
+
mode: 384
|
|
3921
|
+
});
|
|
3922
|
+
}
|
|
3923
|
+
function loadApiKey() {
|
|
3924
|
+
const creds = readCredentials();
|
|
3925
|
+
return creds.apiKey ?? creds.token;
|
|
3926
|
+
}
|
|
3927
|
+
function requireApiKey() {
|
|
3928
|
+
const apiKey = loadApiKey();
|
|
3929
|
+
if (!apiKey) {
|
|
3930
|
+
throw new Error(
|
|
3931
|
+
"API Key \u672A\u8BBE\u7F6E\uFF0C\u8BF7\u5148\u6267\u884C openclaw ntf auth set-api-key <apiKey>\uFF08\u82E5 ntf \u547D\u4EE4\u51B2\u7A81\uFF0C\u53EF\u4F7F\u7528 openclaw phone-notifications auth set-api-key <apiKey>\uFF09"
|
|
3932
|
+
);
|
|
3933
|
+
}
|
|
3934
|
+
return apiKey;
|
|
3935
|
+
}
|
|
3936
|
+
function watchCredentials(onChange) {
|
|
3937
|
+
const path2 = credentialsPath();
|
|
3938
|
+
const dir = dirname2(path2);
|
|
3939
|
+
const filename = basename(path2);
|
|
3940
|
+
let debounceTimer = null;
|
|
3941
|
+
const delayMs = 200;
|
|
3942
|
+
const listener = (_event, changedName) => {
|
|
3943
|
+
if (!changedName || changedName !== filename && !changedName.endsWith(filename)) return;
|
|
3944
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
3945
|
+
debounceTimer = setTimeout(() => {
|
|
3946
|
+
debounceTimer = null;
|
|
3947
|
+
onChange();
|
|
3948
|
+
}, delayMs);
|
|
3949
|
+
};
|
|
3950
|
+
let watcher = null;
|
|
3951
|
+
try {
|
|
3952
|
+
watcher = watch(dir, { persistent: false }, listener);
|
|
3953
|
+
} catch {
|
|
3954
|
+
}
|
|
3955
|
+
return () => {
|
|
3956
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
3957
|
+
watcher?.close();
|
|
3958
|
+
};
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
// src/notification/app-name-map.ts
|
|
3962
|
+
var PLUGIN_STATE_DIR = "phone-notifications";
|
|
3963
|
+
var CACHE_FILE = "app-name-map.json";
|
|
3964
|
+
var BUILTIN_APP_NAME_MAP_URL = getEnvUrls().appNameMapUrl;
|
|
3965
|
+
var APP_NAME_MAP_URL = ("".trim() ? "".trim() : void 0) ?? BUILTIN_APP_NAME_MAP_URL;
|
|
3966
|
+
var APP_NAME_MAP_REFRESH_HOURS = 12;
|
|
3967
|
+
function isRecordOfStrings(v) {
|
|
3968
|
+
if (v === null || typeof v !== "object") return false;
|
|
3969
|
+
for (const val of Object.values(v)) if (typeof val !== "string") return false;
|
|
3970
|
+
return true;
|
|
3971
|
+
}
|
|
3972
|
+
function isAppNameMapApiResponse(v) {
|
|
3973
|
+
if (v === null || typeof v !== "object") return false;
|
|
3974
|
+
const o = v;
|
|
3975
|
+
return Array.isArray(o.data) && o.data.every(
|
|
3976
|
+
(item) => item !== null && typeof item === "object" && typeof item.packageName === "string" && typeof item.appName === "string"
|
|
3977
|
+
);
|
|
3978
|
+
}
|
|
3979
|
+
function getCachePath(stateDir) {
|
|
3980
|
+
return join3(stateDir, "plugins", PLUGIN_STATE_DIR, CACHE_FILE);
|
|
3981
|
+
}
|
|
3982
|
+
function createAppNameMapProvider(opts) {
|
|
3983
|
+
const { stateDir, logger } = opts;
|
|
3984
|
+
const url = APP_NAME_MAP_URL;
|
|
3985
|
+
const refreshHours = APP_NAME_MAP_REFRESH_HOURS;
|
|
3986
|
+
const map = /* @__PURE__ */ new Map();
|
|
3987
|
+
let refreshTimer = null;
|
|
3988
|
+
let stopWatching = null;
|
|
3989
|
+
let inFlightFetch = null;
|
|
3990
|
+
function loadFromDisk() {
|
|
3991
|
+
const path2 = getCachePath(stateDir);
|
|
3992
|
+
if (!existsSync3(path2)) return;
|
|
3993
|
+
try {
|
|
3994
|
+
const raw = JSON.parse(readFileSync3(path2, "utf-8"));
|
|
3995
|
+
if (!isRecordOfStrings(raw)) return;
|
|
3996
|
+
map.clear();
|
|
3997
|
+
for (const [k, v] of Object.entries(raw)) map.set(k, v);
|
|
3998
|
+
logger.info(`[app-name-map] loaded ${map.size} entries from cache: ${path2}`);
|
|
3999
|
+
} catch {
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
async function fetchFromServer() {
|
|
4003
|
+
const apiKey = loadApiKey();
|
|
4004
|
+
if (!url) {
|
|
4005
|
+
logger.warn("[app-name-map] APP_NAME_MAP_URL is empty, skip refresh");
|
|
4006
|
+
return;
|
|
4007
|
+
}
|
|
4008
|
+
if (!apiKey) {
|
|
4009
|
+
logger.info("[app-name-map] api key missing, skip refresh");
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
const rawApiKey = apiKey.startsWith("Bearer ") ? apiKey.slice("Bearer ".length) : apiKey;
|
|
4013
|
+
try {
|
|
4014
|
+
const res = await fetch(url, {
|
|
4015
|
+
method: "POST",
|
|
4016
|
+
headers: { "Content-Type": "application/json", "X-Api-Key-Id": rawApiKey },
|
|
4017
|
+
body: JSON.stringify({
|
|
4018
|
+
platform: ""
|
|
4019
|
+
})
|
|
4020
|
+
});
|
|
4021
|
+
if (!res.ok) {
|
|
4022
|
+
logger.warn(`[app-name-map] refresh failed: HTTP ${res.status} ${res.statusText}`);
|
|
4023
|
+
return;
|
|
4024
|
+
}
|
|
4025
|
+
const body = await res.json();
|
|
4026
|
+
if (!isAppNameMapApiResponse(body) || !body.success || !body.data?.length) {
|
|
4027
|
+
logger.warn("[app-name-map] refresh failed: unexpected response shape or empty data");
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
map.clear();
|
|
4031
|
+
for (const item of body.data) {
|
|
4032
|
+
if (item.packageName && item.appName) map.set(item.packageName, item.appName);
|
|
4033
|
+
}
|
|
4034
|
+
if (map.size === 0) {
|
|
4035
|
+
logger.warn("[app-name-map] refresh succeeded but got 0 entries");
|
|
4036
|
+
return;
|
|
4037
|
+
}
|
|
4038
|
+
const dir = join3(stateDir, "plugins", PLUGIN_STATE_DIR);
|
|
4039
|
+
mkdirSync3(dir, { recursive: true });
|
|
4040
|
+
const cachePath = getCachePath(stateDir);
|
|
4041
|
+
writeFileSync3(cachePath, JSON.stringify(Object.fromEntries(map), null, 2), "utf-8");
|
|
4042
|
+
logger.info(`[app-name-map] refreshed ${map.size} entries from server and saved: ${cachePath}`);
|
|
4043
|
+
} catch (e) {
|
|
4044
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
4045
|
+
logger.warn(`[app-name-map] refresh error: ${message}`);
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
async function ensureOneFetch() {
|
|
4049
|
+
if (inFlightFetch) return inFlightFetch;
|
|
4050
|
+
inFlightFetch = fetchFromServer().finally(() => {
|
|
4051
|
+
inFlightFetch = null;
|
|
4052
|
+
});
|
|
4053
|
+
return inFlightFetch;
|
|
4054
|
+
}
|
|
4055
|
+
return {
|
|
4056
|
+
async resolveDisplayName(packageName) {
|
|
4057
|
+
if (map.has(packageName)) return map.get(packageName);
|
|
4058
|
+
return packageName;
|
|
4059
|
+
},
|
|
4060
|
+
async start() {
|
|
4061
|
+
loadFromDisk();
|
|
4062
|
+
await fetchFromServer();
|
|
4063
|
+
if (!url) return;
|
|
4064
|
+
if (refreshHours > 0) {
|
|
4065
|
+
const ms = refreshHours * 60 * 60 * 1e3;
|
|
4066
|
+
refreshTimer = setInterval(() => fetchFromServer().catch(() => {
|
|
4067
|
+
}), ms);
|
|
4068
|
+
}
|
|
4069
|
+
stopWatching = watchCredentials(() => fetchFromServer().catch(() => {
|
|
4070
|
+
}));
|
|
4071
|
+
},
|
|
4072
|
+
stop() {
|
|
4073
|
+
stopWatching?.();
|
|
4074
|
+
stopWatching = null;
|
|
4075
|
+
if (refreshTimer) {
|
|
4076
|
+
clearInterval(refreshTimer);
|
|
4077
|
+
refreshTimer = null;
|
|
4078
|
+
}
|
|
4079
|
+
map.clear();
|
|
4080
|
+
}
|
|
4081
|
+
};
|
|
4082
|
+
}
|
|
4083
|
+
|
|
3839
4084
|
// src/notification/storage.ts
|
|
3840
4085
|
import {
|
|
3841
4086
|
accessSync,
|
|
3842
|
-
mkdirSync,
|
|
4087
|
+
mkdirSync as mkdirSync4,
|
|
3843
4088
|
appendFileSync,
|
|
3844
4089
|
readdirSync,
|
|
3845
|
-
readFileSync,
|
|
3846
|
-
writeFileSync,
|
|
3847
|
-
existsSync,
|
|
4090
|
+
readFileSync as readFileSync4,
|
|
4091
|
+
writeFileSync as writeFileSync4,
|
|
4092
|
+
existsSync as existsSync4,
|
|
3848
4093
|
rmSync,
|
|
3849
4094
|
constants
|
|
3850
4095
|
} from "fs";
|
|
3851
|
-
import { join } from "path";
|
|
4096
|
+
import { join as join4 } from "path";
|
|
3852
4097
|
var NOTIFICATION_DIR_NAME = "notifications";
|
|
3853
4098
|
var ID_INDEX_DIR_NAME = ".ids";
|
|
3854
4099
|
function getStateFallbackNotificationDir(stateDir) {
|
|
3855
|
-
return
|
|
4100
|
+
return join4(stateDir, "plugins", "phone-notifications", NOTIFICATION_DIR_NAME);
|
|
3856
4101
|
}
|
|
3857
4102
|
function ensureWritableDirectory(dir) {
|
|
3858
4103
|
try {
|
|
3859
|
-
|
|
4104
|
+
mkdirSync4(dir, { recursive: true });
|
|
3860
4105
|
accessSync(dir, constants.R_OK | constants.W_OK);
|
|
3861
4106
|
return true;
|
|
3862
4107
|
} catch {
|
|
@@ -3870,7 +4115,7 @@ function resolveNotificationStorageDir(ctx, logger) {
|
|
|
3870
4115
|
return stateNotifDir;
|
|
3871
4116
|
}
|
|
3872
4117
|
if (ctx.workspaceDir) {
|
|
3873
|
-
const workspaceDir =
|
|
4118
|
+
const workspaceDir = join4(ctx.workspaceDir, NOTIFICATION_DIR_NAME);
|
|
3874
4119
|
if (ensureWritableDirectory(workspaceDir)) {
|
|
3875
4120
|
logger.warn(
|
|
3876
4121
|
`stateDir \u4E0D\u53EF\u7528\uFF0C\u901A\u77E5\u5DF2\u56DE\u9000\u5230 workspace \u8DEF\u5F84: ${workspaceDir}`
|
|
@@ -3881,53 +4126,58 @@ function resolveNotificationStorageDir(ctx, logger) {
|
|
|
3881
4126
|
throw new Error(`\u901A\u77E5\u5B58\u50A8\u76EE\u5F55\u4E0D\u53EF\u7528: ${stateNotifDir}`);
|
|
3882
4127
|
}
|
|
3883
4128
|
var NotificationStorage = class {
|
|
3884
|
-
constructor(dir, config, logger) {
|
|
4129
|
+
constructor(dir, config, logger, resolveDisplayName) {
|
|
3885
4130
|
this.config = config;
|
|
3886
4131
|
this.logger = logger;
|
|
3887
4132
|
this.dir = dir;
|
|
3888
|
-
this.idIndexDir =
|
|
4133
|
+
this.idIndexDir = join4(dir, ID_INDEX_DIR_NAME);
|
|
4134
|
+
this.resolveDisplayName = resolveDisplayName;
|
|
3889
4135
|
}
|
|
3890
4136
|
dir;
|
|
3891
4137
|
idIndexDir;
|
|
3892
4138
|
idCache = /* @__PURE__ */ new Map();
|
|
4139
|
+
resolveDisplayName;
|
|
3893
4140
|
async init() {
|
|
3894
|
-
|
|
3895
|
-
|
|
4141
|
+
mkdirSync4(this.dir, { recursive: true });
|
|
4142
|
+
mkdirSync4(this.idIndexDir, { recursive: true });
|
|
3896
4143
|
}
|
|
3897
4144
|
async ingest(items) {
|
|
3898
4145
|
for (const n of items) {
|
|
3899
|
-
this.writeNotification(n);
|
|
4146
|
+
await this.writeNotification(n);
|
|
3900
4147
|
}
|
|
3901
4148
|
this.prune();
|
|
3902
4149
|
}
|
|
3903
|
-
writeNotification(n) {
|
|
4150
|
+
async writeNotification(n) {
|
|
3904
4151
|
const ts = new Date(n.timestamp);
|
|
3905
4152
|
if (Number.isNaN(ts.getTime())) {
|
|
3906
4153
|
this.logger.warn(`\u5FFD\u7565\u975E\u6CD5 timestamp \u7684\u901A\u77E5: ${n.id}`);
|
|
3907
4154
|
return;
|
|
3908
4155
|
}
|
|
3909
4156
|
const dateKey = this.formatDate(ts);
|
|
3910
|
-
const filePath =
|
|
4157
|
+
const filePath = join4(this.dir, `${dateKey}.json`);
|
|
3911
4158
|
const normalizedId = typeof n.id === "string" ? n.id.trim() : "";
|
|
3912
4159
|
if (normalizedId && this.hasNotificationId(dateKey, normalizedId)) {
|
|
3913
4160
|
return;
|
|
3914
4161
|
}
|
|
4162
|
+
const appName = typeof n.app === "string" && n.app ? n.app : "Unknown";
|
|
4163
|
+
const appDisplayName = this.resolveDisplayName ? await this.resolveDisplayName(appName) : appName;
|
|
3915
4164
|
const entry = {
|
|
3916
|
-
appName
|
|
4165
|
+
appName,
|
|
4166
|
+
appDisplayName,
|
|
3917
4167
|
title: typeof n.title === "string" ? n.title : "",
|
|
3918
4168
|
content: this.buildContent(n),
|
|
3919
4169
|
timestamp: n.timestamp
|
|
3920
4170
|
};
|
|
3921
4171
|
let arr = [];
|
|
3922
|
-
if (
|
|
4172
|
+
if (existsSync4(filePath)) {
|
|
3923
4173
|
try {
|
|
3924
|
-
arr = JSON.parse(
|
|
4174
|
+
arr = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
3925
4175
|
} catch {
|
|
3926
4176
|
arr = [];
|
|
3927
4177
|
}
|
|
3928
4178
|
}
|
|
3929
4179
|
arr.push(entry);
|
|
3930
|
-
|
|
4180
|
+
writeFileSync4(filePath, JSON.stringify(arr, null, 2), "utf-8");
|
|
3931
4181
|
if (normalizedId) {
|
|
3932
4182
|
this.recordNotificationId(dateKey, normalizedId);
|
|
3933
4183
|
}
|
|
@@ -3953,7 +4203,7 @@ var NotificationStorage = class {
|
|
|
3953
4203
|
return `${year}-${month}-${day}`;
|
|
3954
4204
|
}
|
|
3955
4205
|
getIdIndexPath(dateKey) {
|
|
3956
|
-
return
|
|
4206
|
+
return join4(this.idIndexDir, `${dateKey}.ids`);
|
|
3957
4207
|
}
|
|
3958
4208
|
getIdSet(dateKey) {
|
|
3959
4209
|
const cached = this.idCache.get(dateKey);
|
|
@@ -3962,8 +4212,8 @@ var NotificationStorage = class {
|
|
|
3962
4212
|
}
|
|
3963
4213
|
const idPath = this.getIdIndexPath(dateKey);
|
|
3964
4214
|
const ids = /* @__PURE__ */ new Set();
|
|
3965
|
-
if (
|
|
3966
|
-
const lines =
|
|
4215
|
+
if (existsSync4(idPath)) {
|
|
4216
|
+
const lines = readFileSync4(idPath, "utf-8").split(/\r?\n/);
|
|
3967
4217
|
for (const line of lines) {
|
|
3968
4218
|
const id = line.trim();
|
|
3969
4219
|
if (id) {
|
|
@@ -4002,10 +4252,10 @@ var NotificationStorage = class {
|
|
|
4002
4252
|
if (entry.isFile()) {
|
|
4003
4253
|
const match = dateFilePattern.exec(entry.name);
|
|
4004
4254
|
if (match && match[1] < cutoffDate) {
|
|
4005
|
-
rmSync(
|
|
4255
|
+
rmSync(join4(this.dir, entry.name), { force: true });
|
|
4006
4256
|
}
|
|
4007
4257
|
} else if (entry.isDirectory() && dateDirPattern.test(entry.name) && entry.name < cutoffDate) {
|
|
4008
|
-
rmSync(
|
|
4258
|
+
rmSync(join4(this.dir, entry.name), { recursive: true, force: true });
|
|
4009
4259
|
}
|
|
4010
4260
|
}
|
|
4011
4261
|
} catch {
|
|
@@ -4018,7 +4268,7 @@ var NotificationStorage = class {
|
|
|
4018
4268
|
if (!entry.isFile()) continue;
|
|
4019
4269
|
const match = /^(\d{4}-\d{2}-\d{2})\.ids$/.exec(entry.name);
|
|
4020
4270
|
if (match && match[1] < cutoffDate) {
|
|
4021
|
-
rmSync(
|
|
4271
|
+
rmSync(join4(this.idIndexDir, entry.name), { force: true });
|
|
4022
4272
|
this.idCache.delete(match[1]);
|
|
4023
4273
|
}
|
|
4024
4274
|
}
|
|
@@ -4031,24 +4281,24 @@ var NotificationStorage = class {
|
|
|
4031
4281
|
};
|
|
4032
4282
|
|
|
4033
4283
|
// src/cli/auth.ts
|
|
4034
|
-
import { existsSync as
|
|
4284
|
+
import { existsSync as existsSync6, rmSync as rmSync2 } from "fs";
|
|
4035
4285
|
|
|
4036
4286
|
// src/cli/helpers.ts
|
|
4037
|
-
import { existsSync as
|
|
4038
|
-
import { join as
|
|
4287
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "fs";
|
|
4288
|
+
import { join as join5 } from "path";
|
|
4039
4289
|
function resolveNotificationsDir(ctx) {
|
|
4040
4290
|
if (ctx.stateDir) {
|
|
4041
|
-
const dir =
|
|
4291
|
+
const dir = join5(
|
|
4042
4292
|
ctx.stateDir,
|
|
4043
4293
|
"plugins",
|
|
4044
4294
|
"phone-notifications",
|
|
4045
4295
|
"notifications"
|
|
4046
4296
|
);
|
|
4047
|
-
if (
|
|
4297
|
+
if (existsSync5(dir)) return dir;
|
|
4048
4298
|
}
|
|
4049
4299
|
if (ctx.workspaceDir) {
|
|
4050
|
-
const dir =
|
|
4051
|
-
if (
|
|
4300
|
+
const dir = join5(ctx.workspaceDir, "notifications");
|
|
4301
|
+
if (existsSync5(dir)) return dir;
|
|
4052
4302
|
}
|
|
4053
4303
|
return null;
|
|
4054
4304
|
}
|
|
@@ -4063,10 +4313,10 @@ function listDateKeys(dir) {
|
|
|
4063
4313
|
return keys.sort().reverse();
|
|
4064
4314
|
}
|
|
4065
4315
|
function readDateFile(dir, dateKey) {
|
|
4066
|
-
const filePath =
|
|
4067
|
-
if (!
|
|
4316
|
+
const filePath = join5(dir, `${dateKey}.json`);
|
|
4317
|
+
if (!existsSync5(filePath)) return [];
|
|
4068
4318
|
try {
|
|
4069
|
-
return JSON.parse(
|
|
4319
|
+
return JSON.parse(readFileSync5(filePath, "utf-8"));
|
|
4070
4320
|
} catch {
|
|
4071
4321
|
return [];
|
|
4072
4322
|
}
|
|
@@ -4096,76 +4346,44 @@ function exitError(code, message) {
|
|
|
4096
4346
|
process.exit(1);
|
|
4097
4347
|
}
|
|
4098
4348
|
|
|
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
4349
|
// src/cli/auth.ts
|
|
4142
4350
|
function registerAuthCli(program) {
|
|
4143
4351
|
const auth = program.command("auth").description("\u7528\u6237\u8BA4\u8BC1\u7BA1\u7406");
|
|
4144
|
-
auth.command("set-
|
|
4145
|
-
writeCredentials({ ...readCredentials(), token });
|
|
4352
|
+
auth.command("set-api-key <apiKey>").description("\u8BBE\u7F6E\u7528\u6237 API Key\uFF08\u6301\u4E45\u5316\u5230\u672C\u5730\u914D\u7F6E\uFF09").action((apiKey) => {
|
|
4353
|
+
writeCredentials({ ...readCredentials(), apiKey, token: void 0 });
|
|
4354
|
+
output({
|
|
4355
|
+
ok: true,
|
|
4356
|
+
apiKey: apiKey.slice(0, 8) + "\u2026",
|
|
4357
|
+
storedAt: credentialsPath()
|
|
4358
|
+
});
|
|
4359
|
+
});
|
|
4360
|
+
auth.command("set-token <token>").description("\uFF08\u517C\u5BB9\uFF09\u8BBE\u7F6E\u7528\u6237 API Key\uFF08\u65E7\u547D\u4EE4\u540D\uFF09").action((token) => {
|
|
4361
|
+
writeCredentials({ ...readCredentials(), apiKey: token, token: void 0 });
|
|
4146
4362
|
output({
|
|
4147
4363
|
ok: true,
|
|
4148
|
-
|
|
4364
|
+
apiKey: token.slice(0, 8) + "\u2026",
|
|
4149
4365
|
storedAt: credentialsPath()
|
|
4150
4366
|
});
|
|
4151
4367
|
});
|
|
4152
4368
|
auth.command("show").description("\u67E5\u770B\u5F53\u524D\u8BA4\u8BC1\u72B6\u6001").action(() => {
|
|
4153
4369
|
const creds = readCredentials();
|
|
4154
|
-
|
|
4370
|
+
const apiKey = creds.apiKey ?? creds.token;
|
|
4371
|
+
if (apiKey) {
|
|
4155
4372
|
output({
|
|
4156
4373
|
ok: true,
|
|
4157
|
-
|
|
4158
|
-
|
|
4374
|
+
hasApiKey: true,
|
|
4375
|
+
apiKey: apiKey.slice(0, 8) + "\u2026",
|
|
4159
4376
|
storedAt: credentialsPath()
|
|
4160
4377
|
});
|
|
4161
4378
|
} else {
|
|
4162
|
-
output({ ok: true,
|
|
4379
|
+
output({ ok: true, hasApiKey: false });
|
|
4163
4380
|
}
|
|
4164
4381
|
});
|
|
4165
4382
|
auth.command("clear").description("\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u8BA4\u8BC1\u4FE1\u606F").action(() => {
|
|
4166
4383
|
const path2 = credentialsPath();
|
|
4167
|
-
if (
|
|
4384
|
+
if (existsSync6(path2)) {
|
|
4168
4385
|
const creds = readCredentials();
|
|
4386
|
+
delete creds.apiKey;
|
|
4169
4387
|
delete creds.token;
|
|
4170
4388
|
if (Object.keys(creds).length === 0) {
|
|
4171
4389
|
rmSync2(path2, { force: true });
|
|
@@ -4263,22 +4481,22 @@ function registerNtfStats(ntf, ctx) {
|
|
|
4263
4481
|
}
|
|
4264
4482
|
|
|
4265
4483
|
// src/cli/ntf-sync.ts
|
|
4266
|
-
import { existsSync as
|
|
4267
|
-
import { join as
|
|
4484
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
4485
|
+
import { join as join6 } from "path";
|
|
4268
4486
|
function checkpointPath(dir) {
|
|
4269
|
-
return
|
|
4487
|
+
return join6(dir, ".checkpoint.json");
|
|
4270
4488
|
}
|
|
4271
4489
|
function readCheckpoint(dir) {
|
|
4272
4490
|
const p = checkpointPath(dir);
|
|
4273
|
-
if (!
|
|
4491
|
+
if (!existsSync7(p)) return {};
|
|
4274
4492
|
try {
|
|
4275
|
-
return JSON.parse(
|
|
4493
|
+
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
4276
4494
|
} catch {
|
|
4277
4495
|
return {};
|
|
4278
4496
|
}
|
|
4279
4497
|
}
|
|
4280
4498
|
function writeCheckpoint(dir, data) {
|
|
4281
|
-
|
|
4499
|
+
writeFileSync5(checkpointPath(dir), JSON.stringify(data, null, 2), "utf-8");
|
|
4282
4500
|
}
|
|
4283
4501
|
function registerNtfSync(ntf, ctx) {
|
|
4284
4502
|
const sync = ntf.command("sync").description("\u540C\u6B65\u901A\u77E5\u5230\u8BB0\u5FC6\u7CFB\u7EDF");
|
|
@@ -4352,30 +4570,30 @@ function registerNtfSync(ntf, ctx) {
|
|
|
4352
4570
|
|
|
4353
4571
|
// src/cli/ntf-monitor.ts
|
|
4354
4572
|
import {
|
|
4355
|
-
existsSync as
|
|
4356
|
-
mkdirSync as
|
|
4357
|
-
readFileSync as
|
|
4358
|
-
writeFileSync as
|
|
4573
|
+
existsSync as existsSync8,
|
|
4574
|
+
mkdirSync as mkdirSync5,
|
|
4575
|
+
readFileSync as readFileSync7,
|
|
4576
|
+
writeFileSync as writeFileSync6,
|
|
4359
4577
|
rmSync as rmSync3,
|
|
4360
4578
|
readdirSync as readdirSync3
|
|
4361
4579
|
} from "fs";
|
|
4362
|
-
import { join as
|
|
4580
|
+
import { join as join7 } from "path";
|
|
4363
4581
|
function tasksDir(ctx) {
|
|
4364
4582
|
const base = ctx.workspaceDir || ctx.stateDir;
|
|
4365
4583
|
if (!base) throw new Error("workspaceDir and stateDir both unavailable");
|
|
4366
|
-
return
|
|
4584
|
+
return join7(base, "tasks");
|
|
4367
4585
|
}
|
|
4368
4586
|
function readMeta(taskDir) {
|
|
4369
|
-
const metaPath =
|
|
4370
|
-
if (!
|
|
4587
|
+
const metaPath = join7(taskDir, "meta.json");
|
|
4588
|
+
if (!existsSync8(metaPath)) return null;
|
|
4371
4589
|
try {
|
|
4372
|
-
return JSON.parse(
|
|
4590
|
+
return JSON.parse(readFileSync7(metaPath, "utf-8"));
|
|
4373
4591
|
} catch {
|
|
4374
4592
|
return null;
|
|
4375
4593
|
}
|
|
4376
4594
|
}
|
|
4377
4595
|
function writeMeta(taskDir, meta) {
|
|
4378
|
-
|
|
4596
|
+
writeFileSync6(join7(taskDir, "meta.json"), JSON.stringify(meta, null, 2), "utf-8");
|
|
4379
4597
|
}
|
|
4380
4598
|
function generateFetchPy(name, matchRules) {
|
|
4381
4599
|
const appName = matchRules.appName || "";
|
|
@@ -4452,27 +4670,27 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
4452
4670
|
const monitor = ntf.command("monitor").description("\u901A\u77E5\u76D1\u63A7\u4EFB\u52A1\u7BA1\u7406");
|
|
4453
4671
|
monitor.command("list").description("\u5217\u51FA\u6240\u6709\u76D1\u63A7\u4EFB\u52A1").action(() => {
|
|
4454
4672
|
const dir = tasksDir(ctx);
|
|
4455
|
-
if (!
|
|
4673
|
+
if (!existsSync8(dir)) {
|
|
4456
4674
|
output({ ok: true, tasks: [] });
|
|
4457
4675
|
return;
|
|
4458
4676
|
}
|
|
4459
4677
|
const tasks = [];
|
|
4460
4678
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
4461
4679
|
if (!entry.isDirectory()) continue;
|
|
4462
|
-
const meta = readMeta(
|
|
4680
|
+
const meta = readMeta(join7(dir, entry.name));
|
|
4463
4681
|
if (meta) tasks.push(meta);
|
|
4464
4682
|
}
|
|
4465
4683
|
output({ ok: true, tasks });
|
|
4466
4684
|
});
|
|
4467
4685
|
monitor.command("show <name>").description("\u67E5\u770B\u76D1\u63A7\u4EFB\u52A1\u8BE6\u60C5").action((name) => {
|
|
4468
|
-
const taskDir =
|
|
4686
|
+
const taskDir = join7(tasksDir(ctx), name);
|
|
4469
4687
|
const meta = readMeta(taskDir);
|
|
4470
4688
|
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
4471
|
-
const checkpointPath2 =
|
|
4689
|
+
const checkpointPath2 = join7(taskDir, "checkpoint.json");
|
|
4472
4690
|
let checkpoint = {};
|
|
4473
|
-
if (
|
|
4691
|
+
if (existsSync8(checkpointPath2)) {
|
|
4474
4692
|
try {
|
|
4475
|
-
checkpoint = JSON.parse(
|
|
4693
|
+
checkpoint = JSON.parse(readFileSync7(checkpointPath2, "utf-8"));
|
|
4476
4694
|
} catch {
|
|
4477
4695
|
}
|
|
4478
4696
|
}
|
|
@@ -4487,8 +4705,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
4487
4705
|
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
4706
|
(name, opts) => {
|
|
4489
4707
|
const dir = tasksDir(ctx);
|
|
4490
|
-
const taskDir =
|
|
4491
|
-
if (
|
|
4708
|
+
const taskDir = join7(dir, name);
|
|
4709
|
+
if (existsSync8(taskDir)) {
|
|
4492
4710
|
exitError("ALREADY_EXISTS", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u5DF2\u5B58\u5728`);
|
|
4493
4711
|
}
|
|
4494
4712
|
let matchRules;
|
|
@@ -4500,7 +4718,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
4500
4718
|
"match-rules \u5FC5\u987B\u662F\u5408\u6CD5\u7684 JSON"
|
|
4501
4719
|
);
|
|
4502
4720
|
}
|
|
4503
|
-
|
|
4721
|
+
mkdirSync5(taskDir, { recursive: true });
|
|
4504
4722
|
const meta = {
|
|
4505
4723
|
name,
|
|
4506
4724
|
description: opts.description,
|
|
@@ -4510,13 +4728,13 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
4510
4728
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4511
4729
|
};
|
|
4512
4730
|
writeMeta(taskDir, meta);
|
|
4513
|
-
|
|
4514
|
-
|
|
4731
|
+
writeFileSync6(
|
|
4732
|
+
join7(taskDir, "fetch.py"),
|
|
4515
4733
|
generateFetchPy(name, matchRules),
|
|
4516
4734
|
"utf-8"
|
|
4517
4735
|
);
|
|
4518
|
-
|
|
4519
|
-
|
|
4736
|
+
writeFileSync6(
|
|
4737
|
+
join7(taskDir, "README.md"),
|
|
4520
4738
|
generateReadme(name, opts.description),
|
|
4521
4739
|
"utf-8"
|
|
4522
4740
|
);
|
|
@@ -4544,8 +4762,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
4544
4762
|
}
|
|
4545
4763
|
);
|
|
4546
4764
|
monitor.command("delete <name>").description("\u5220\u9664\u76D1\u63A7\u4EFB\u52A1").option("--yes", "\u8DF3\u8FC7\u786E\u8BA4").action((name, opts) => {
|
|
4547
|
-
const taskDir =
|
|
4548
|
-
if (!
|
|
4765
|
+
const taskDir = join7(tasksDir(ctx), name);
|
|
4766
|
+
if (!existsSync8(taskDir)) {
|
|
4549
4767
|
exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
4550
4768
|
}
|
|
4551
4769
|
if (!opts.yes) {
|
|
@@ -4570,7 +4788,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
4570
4788
|
});
|
|
4571
4789
|
});
|
|
4572
4790
|
monitor.command("enable <name>").description("\u542F\u7528\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
4573
|
-
const taskDir =
|
|
4791
|
+
const taskDir = join7(tasksDir(ctx), name);
|
|
4574
4792
|
const meta = readMeta(taskDir);
|
|
4575
4793
|
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
4576
4794
|
meta.enabled = true;
|
|
@@ -4578,7 +4796,7 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
4578
4796
|
output({ ok: true, name, enabled: true });
|
|
4579
4797
|
});
|
|
4580
4798
|
monitor.command("disable <name>").description("\u6682\u505C\u76D1\u63A7\u4EFB\u52A1").action((name) => {
|
|
4581
|
-
const taskDir =
|
|
4799
|
+
const taskDir = join7(tasksDir(ctx), name);
|
|
4582
4800
|
const meta = readMeta(taskDir);
|
|
4583
4801
|
if (!meta) exitError("NOT_FOUND", `\u76D1\u63A7\u4EFB\u52A1 '${name}' \u4E0D\u5B58\u5728`);
|
|
4584
4802
|
meta.enabled = false;
|
|
@@ -4588,8 +4806,8 @@ function registerNtfMonitor(ntf, ctx) {
|
|
|
4588
4806
|
}
|
|
4589
4807
|
|
|
4590
4808
|
// src/cli/light-rules.ts
|
|
4591
|
-
import { existsSync as
|
|
4592
|
-
import { join as
|
|
4809
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
4810
|
+
import { join as join8 } from "path";
|
|
4593
4811
|
|
|
4594
4812
|
// src/light/validators.ts
|
|
4595
4813
|
var VALID_MODES = ["wave", "breath", "strobe", "steady", "wave_rainbow"];
|
|
@@ -4654,15 +4872,15 @@ var LIGHT_RULES_SECTION = "## \u706F\u6548\u89C4\u5219";
|
|
|
4654
4872
|
function memoryPath(ctx) {
|
|
4655
4873
|
const base = ctx.workspaceDir || ctx.stateDir;
|
|
4656
4874
|
if (!base) throw new Error("workspaceDir and stateDir both unavailable");
|
|
4657
|
-
return
|
|
4875
|
+
return join8(base, "MEMORY.md");
|
|
4658
4876
|
}
|
|
4659
4877
|
function readMemory(ctx) {
|
|
4660
4878
|
const p = memoryPath(ctx);
|
|
4661
|
-
if (!
|
|
4662
|
-
return
|
|
4879
|
+
if (!existsSync9(p)) return "";
|
|
4880
|
+
return readFileSync8(p, "utf-8");
|
|
4663
4881
|
}
|
|
4664
4882
|
function writeMemory(ctx, content) {
|
|
4665
|
-
|
|
4883
|
+
writeFileSync7(memoryPath(ctx), content, "utf-8");
|
|
4666
4884
|
}
|
|
4667
4885
|
function parseLightRules(content) {
|
|
4668
4886
|
const rules = [];
|
|
@@ -4825,14 +5043,14 @@ function registerLightRules(program, ctx) {
|
|
|
4825
5043
|
// src/cli/light-send.ts
|
|
4826
5044
|
function registerLightSend(light) {
|
|
4827
5045
|
light.command("send").description("\u53D1\u9001\u706F\u6548\u6307\u4EE4\u5230\u786C\u4EF6\u8BBE\u5907").requiredOption("--segments <json>", "\u706F\u6548\u53C2\u6570 JSON").option("--repeat", "\u65E0\u9650\u5FAA\u73AF\u64AD\u653E\uFF08\u9ED8\u8BA4\u4EC5\u64AD\u653E\u4E00\u8F6E\uFF09").action(async (opts) => {
|
|
4828
|
-
let
|
|
5046
|
+
let apiKey;
|
|
4829
5047
|
try {
|
|
4830
|
-
|
|
5048
|
+
apiKey = requireApiKey();
|
|
4831
5049
|
} catch (e) {
|
|
4832
5050
|
exitError("AUTH_REQUIRED", e.message);
|
|
4833
5051
|
}
|
|
4834
5052
|
const segments = parseAndValidateSegments(opts.segments);
|
|
4835
|
-
const result = await sendLightEffect(
|
|
5053
|
+
const result = await sendLightEffect(apiKey, segments, void 0, opts.repeat);
|
|
4836
5054
|
if (!result.ok) {
|
|
4837
5055
|
exitError("HTTP_ERROR", `\u8BF7\u6C42\u5931\u8D25: ${result.status} ${result.error}`);
|
|
4838
5056
|
}
|
|
@@ -4841,15 +5059,15 @@ function registerLightSend(light) {
|
|
|
4841
5059
|
}
|
|
4842
5060
|
|
|
4843
5061
|
// src/cli/tunnel-status.ts
|
|
4844
|
-
import { existsSync as
|
|
4845
|
-
import { join as
|
|
4846
|
-
var STATUS_REL_PATH =
|
|
5062
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
5063
|
+
import { join as join9 } from "path";
|
|
5064
|
+
var STATUS_REL_PATH = join9("plugins", "phone-notifications", "tunnel-status.json");
|
|
4847
5065
|
function readTunnelStatus(ctx) {
|
|
4848
5066
|
if (!ctx.stateDir) return null;
|
|
4849
|
-
const filePath =
|
|
4850
|
-
if (!
|
|
5067
|
+
const filePath = join9(ctx.stateDir, STATUS_REL_PATH);
|
|
5068
|
+
if (!existsSync10(filePath)) return null;
|
|
4851
5069
|
try {
|
|
4852
|
-
return JSON.parse(
|
|
5070
|
+
return JSON.parse(readFileSync9(filePath, "utf-8"));
|
|
4853
5071
|
} catch {
|
|
4854
5072
|
return null;
|
|
4855
5073
|
}
|
|
@@ -4870,18 +5088,18 @@ function formatMessage(status) {
|
|
|
4870
5088
|
}
|
|
4871
5089
|
function registerTunnelStatus(ntf, ctx) {
|
|
4872
5090
|
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 =
|
|
4874
|
-
const
|
|
5091
|
+
const tunnelUrl = getEnvUrls().relayTunnelUrl;
|
|
5092
|
+
const apiKey = loadApiKey();
|
|
4875
5093
|
if (!tunnelUrl) {
|
|
4876
5094
|
exitError(
|
|
4877
5095
|
"TUNNEL_NOT_CONFIGURED",
|
|
4878
5096
|
"RELAY_TUNNEL_URL \u672A\u914D\u7F6E\uFF0C\u96A7\u9053\u529F\u80FD\u672A\u542F\u7528\u3002\u8BF7\u5728\u6784\u5EFA\u65F6\u8BBE\u7F6E RELAY_TUNNEL_URL \u73AF\u5883\u53D8\u91CF\u3002"
|
|
4879
5097
|
);
|
|
4880
5098
|
}
|
|
4881
|
-
if (!
|
|
5099
|
+
if (!apiKey) {
|
|
4882
5100
|
exitError(
|
|
4883
5101
|
"TOKEN_MISSING",
|
|
4884
|
-
"
|
|
5102
|
+
"API Key \u672A\u8BBE\u7F6E\uFF0C\u96A7\u9053\u65E0\u6CD5\u8FDE\u63A5\u3002\u8BF7\u6267\u884C openclaw ntf auth set-api-key <apiKey>"
|
|
4885
5103
|
);
|
|
4886
5104
|
}
|
|
4887
5105
|
const status = readTunnelStatus(ctx);
|
|
@@ -4917,17 +5135,17 @@ function registerNtfStoragePath(ntf, ctx) {
|
|
|
4917
5135
|
}
|
|
4918
5136
|
|
|
4919
5137
|
// src/cli/log-search.ts
|
|
4920
|
-
import { existsSync as
|
|
4921
|
-
import { join as
|
|
5138
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync4 } from "fs";
|
|
5139
|
+
import { join as join10 } from "path";
|
|
4922
5140
|
function resolveLogsDir(ctx) {
|
|
4923
5141
|
if (ctx.stateDir) {
|
|
4924
|
-
const dir =
|
|
5142
|
+
const dir = join10(
|
|
4925
5143
|
ctx.stateDir,
|
|
4926
5144
|
"plugins",
|
|
4927
5145
|
"phone-notifications",
|
|
4928
5146
|
"logs"
|
|
4929
5147
|
);
|
|
4930
|
-
if (
|
|
5148
|
+
if (existsSync11(dir)) return dir;
|
|
4931
5149
|
}
|
|
4932
5150
|
return null;
|
|
4933
5151
|
}
|
|
@@ -4942,9 +5160,9 @@ function listLogDateKeys(dir) {
|
|
|
4942
5160
|
return keys.sort().reverse();
|
|
4943
5161
|
}
|
|
4944
5162
|
function collectLogLines(dir, dateKey, keyword, limit, collected) {
|
|
4945
|
-
const filePath =
|
|
4946
|
-
if (!
|
|
4947
|
-
const content =
|
|
5163
|
+
const filePath = join10(dir, `${dateKey}.log`);
|
|
5164
|
+
if (!existsSync11(filePath)) return;
|
|
5165
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4948
5166
|
const lowerKeyword = keyword?.toLowerCase();
|
|
4949
5167
|
for (const line of content.split("\n")) {
|
|
4950
5168
|
if (collected.length >= limit) return;
|
|
@@ -4975,13 +5193,47 @@ function registerLogSearch(ntf, ctx) {
|
|
|
4975
5193
|
);
|
|
4976
5194
|
}
|
|
4977
5195
|
|
|
5196
|
+
// src/cli/env.ts
|
|
5197
|
+
function registerEnvCli(ntf) {
|
|
5198
|
+
const env = ntf.command("env").description("\u73AF\u5883\u7BA1\u7406\uFF08\u5207\u6362 development / production\uFF09");
|
|
5199
|
+
env.command("show").description("\u663E\u793A\u5F53\u524D\u73AF\u5883\u53CA\u5BF9\u5E94\u7684\u63A5\u53E3\u5730\u5740").action(() => {
|
|
5200
|
+
const current = loadEnvName();
|
|
5201
|
+
const urls = getEnvUrls(current);
|
|
5202
|
+
output({
|
|
5203
|
+
ok: true,
|
|
5204
|
+
env: current,
|
|
5205
|
+
lightApiUrl: urls.lightApiUrl,
|
|
5206
|
+
relayTunnelUrl: urls.relayTunnelUrl,
|
|
5207
|
+
availableEnvs: getAvailableEnvs()
|
|
5208
|
+
});
|
|
5209
|
+
});
|
|
5210
|
+
env.command("switch <env>").description("\u5207\u6362\u73AF\u5883\uFF08development / production\uFF09").action((envName) => {
|
|
5211
|
+
const available = getAvailableEnvs();
|
|
5212
|
+
if (!available.includes(envName)) {
|
|
5213
|
+
exitError(
|
|
5214
|
+
"INVALID_ENV",
|
|
5215
|
+
`\u65E0\u6548\u7684\u73AF\u5883\u540D\u79F0: ${envName}\uFF0C\u53EF\u9009\u503C: ${available.join(", ")}`
|
|
5216
|
+
);
|
|
5217
|
+
}
|
|
5218
|
+
saveEnvName(envName);
|
|
5219
|
+
const urls = getEnvUrls(envName);
|
|
5220
|
+
output({
|
|
5221
|
+
ok: true,
|
|
5222
|
+
message: `\u5DF2\u5207\u6362\u5230 ${envName} \u73AF\u5883`,
|
|
5223
|
+
env: envName,
|
|
5224
|
+
lightApiUrl: urls.lightApiUrl,
|
|
5225
|
+
relayTunnelUrl: urls.relayTunnelUrl
|
|
5226
|
+
});
|
|
5227
|
+
});
|
|
5228
|
+
}
|
|
5229
|
+
|
|
4978
5230
|
// src/version.ts
|
|
4979
|
-
import { readFileSync as
|
|
5231
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4980
5232
|
function readPluginVersion() {
|
|
4981
5233
|
try {
|
|
4982
5234
|
const packageJsonUrl = new URL("../package.json", import.meta.url);
|
|
4983
5235
|
const packageJson = JSON.parse(
|
|
4984
|
-
|
|
5236
|
+
readFileSync11(packageJsonUrl, "utf-8")
|
|
4985
5237
|
);
|
|
4986
5238
|
return packageJson.version ?? "unknown";
|
|
4987
5239
|
} catch {
|
|
@@ -5003,22 +5255,23 @@ function registerAllCli(program, ctx, rootCommandName = "ntf") {
|
|
|
5003
5255
|
registerNtfStoragePath(ntf, ctx);
|
|
5004
5256
|
registerLogSearch(ntf, ctx);
|
|
5005
5257
|
registerTunnelStatus(ntf, ctx);
|
|
5258
|
+
registerEnvCli(ntf);
|
|
5006
5259
|
}
|
|
5007
5260
|
|
|
5008
5261
|
// src/tunnel/service.ts
|
|
5009
5262
|
import {
|
|
5010
5263
|
closeSync,
|
|
5011
|
-
mkdirSync as
|
|
5264
|
+
mkdirSync as mkdirSync7,
|
|
5012
5265
|
openSync,
|
|
5013
|
-
readFileSync as
|
|
5266
|
+
readFileSync as readFileSync12,
|
|
5014
5267
|
unlinkSync,
|
|
5015
|
-
writeFileSync as
|
|
5268
|
+
writeFileSync as writeFileSync9
|
|
5016
5269
|
} from "fs";
|
|
5017
|
-
import { dirname as
|
|
5270
|
+
import { dirname as dirname4, join as join11 } from "path";
|
|
5018
5271
|
|
|
5019
5272
|
// src/tunnel/relay-client.ts
|
|
5020
|
-
import { writeFileSync as
|
|
5021
|
-
import { dirname as
|
|
5273
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
5274
|
+
import { dirname as dirname3 } from "path";
|
|
5022
5275
|
|
|
5023
5276
|
// node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
|
|
5024
5277
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -5050,8 +5303,8 @@ var RelayClient = class {
|
|
|
5050
5303
|
lastDisconnectReason
|
|
5051
5304
|
};
|
|
5052
5305
|
try {
|
|
5053
|
-
|
|
5054
|
-
|
|
5306
|
+
mkdirSync6(dirname3(this.opts.statusFilePath), { recursive: true });
|
|
5307
|
+
writeFileSync8(this.opts.statusFilePath, JSON.stringify(info, null, 2));
|
|
5055
5308
|
} catch {
|
|
5056
5309
|
}
|
|
5057
5310
|
}
|
|
@@ -5121,9 +5374,14 @@ var RelayClient = class {
|
|
|
5121
5374
|
resolve();
|
|
5122
5375
|
}
|
|
5123
5376
|
};
|
|
5124
|
-
const
|
|
5377
|
+
const rawApiKey = this.opts.apiKey.startsWith("Bearer ") ? this.opts.apiKey.slice("Bearer ".length) : this.opts.apiKey;
|
|
5378
|
+
const wsUrl = new URL(this.opts.tunnelUrl);
|
|
5379
|
+
if (!wsUrl.searchParams.get("apiKey")) {
|
|
5380
|
+
wsUrl.searchParams.set("apiKey", rawApiKey);
|
|
5381
|
+
}
|
|
5382
|
+
const ws = new wrapper_default(wsUrl.toString(), {
|
|
5125
5383
|
headers: {
|
|
5126
|
-
|
|
5384
|
+
"X-Api-Key-Id": rawApiKey
|
|
5127
5385
|
}
|
|
5128
5386
|
});
|
|
5129
5387
|
this.ws = ws;
|
|
@@ -5897,7 +6155,7 @@ function createTunnelService(opts) {
|
|
|
5897
6155
|
}
|
|
5898
6156
|
function readLockOwner(filePath) {
|
|
5899
6157
|
try {
|
|
5900
|
-
const parsed = JSON.parse(
|
|
6158
|
+
const parsed = JSON.parse(readFileSync12(filePath, "utf-8"));
|
|
5901
6159
|
return typeof parsed.pid === "number" ? parsed.pid : null;
|
|
5902
6160
|
} catch {
|
|
5903
6161
|
return null;
|
|
@@ -5922,11 +6180,11 @@ function createTunnelService(opts) {
|
|
|
5922
6180
|
}
|
|
5923
6181
|
}
|
|
5924
6182
|
function acquireLock(filePath) {
|
|
5925
|
-
|
|
6183
|
+
mkdirSync7(dirname4(filePath), { recursive: true });
|
|
5926
6184
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5927
6185
|
try {
|
|
5928
6186
|
const fd = openSync(filePath, "wx", 384);
|
|
5929
|
-
|
|
6187
|
+
writeFileSync9(
|
|
5930
6188
|
fd,
|
|
5931
6189
|
JSON.stringify({
|
|
5932
6190
|
pid: process.pid,
|
|
@@ -5975,27 +6233,27 @@ function createTunnelService(opts) {
|
|
|
5975
6233
|
proxy?.cleanup();
|
|
5976
6234
|
proxy = null;
|
|
5977
6235
|
client = null;
|
|
5978
|
-
const
|
|
5979
|
-
if (!
|
|
6236
|
+
const apiKey = loadApiKey();
|
|
6237
|
+
if (!apiKey) {
|
|
5980
6238
|
opts.logger.warn(
|
|
5981
|
-
"Relay tunnel:
|
|
6239
|
+
"Relay tunnel: apiKey \u672A\u8BBE\u7F6E\uFF0C\u8DF3\u8FC7\u96A7\u9053\u8FDE\u63A5\u3002\u8BF7\u6267\u884C openclaw ntf auth set-api-key <apiKey>"
|
|
5982
6240
|
);
|
|
5983
6241
|
return;
|
|
5984
6242
|
}
|
|
5985
6243
|
const { logger } = opts;
|
|
5986
|
-
const baseStateDir =
|
|
6244
|
+
const baseStateDir = join11(ctx.stateDir, "plugins", "phone-notifications");
|
|
5987
6245
|
logger.info(
|
|
5988
6246
|
`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
6247
|
);
|
|
5990
|
-
const statusFilePath =
|
|
5991
|
-
const lockPath =
|
|
6248
|
+
const statusFilePath = join11(baseStateDir, "tunnel-status.json");
|
|
6249
|
+
const lockPath = join11(baseStateDir, "relay-tunnel.lock");
|
|
5992
6250
|
if (!acquireLock(lockPath)) {
|
|
5993
6251
|
return;
|
|
5994
6252
|
}
|
|
5995
6253
|
try {
|
|
5996
6254
|
client = new RelayClient({
|
|
5997
6255
|
tunnelUrl: opts.tunnelUrl,
|
|
5998
|
-
|
|
6256
|
+
apiKey,
|
|
5999
6257
|
heartbeatSec: opts.heartbeatSec ?? DEFAULT_HEARTBEAT_SEC,
|
|
6000
6258
|
reconnectBackoffMs: opts.reconnectBackoffMs ?? DEFAULT_RECONNECT_BACKOFF_MS,
|
|
6001
6259
|
statusFilePath,
|
|
@@ -6036,13 +6294,13 @@ function createTunnelService(opts) {
|
|
|
6036
6294
|
}
|
|
6037
6295
|
|
|
6038
6296
|
// src/logger.ts
|
|
6039
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
6040
|
-
import { join as
|
|
6297
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync8 } from "fs";
|
|
6298
|
+
import { join as join12 } from "path";
|
|
6041
6299
|
var PluginFileLogger = class {
|
|
6042
6300
|
constructor(upstream, stateDir) {
|
|
6043
6301
|
this.upstream = upstream;
|
|
6044
|
-
this.logsDir =
|
|
6045
|
-
|
|
6302
|
+
this.logsDir = join12(stateDir, "plugins", "phone-notifications", "logs");
|
|
6303
|
+
mkdirSync8(this.logsDir, { recursive: true });
|
|
6046
6304
|
}
|
|
6047
6305
|
logsDir;
|
|
6048
6306
|
info(msg) {
|
|
@@ -6064,7 +6322,7 @@ var PluginFileLogger = class {
|
|
|
6064
6322
|
const line = `${time} [${level}] ${msg}
|
|
6065
6323
|
`;
|
|
6066
6324
|
try {
|
|
6067
|
-
appendFileSync2(
|
|
6325
|
+
appendFileSync2(join12(this.logsDir, `${dateKey}.log`), line);
|
|
6068
6326
|
} catch {
|
|
6069
6327
|
}
|
|
6070
6328
|
}
|
|
@@ -6101,7 +6359,7 @@ function resolveLocalGatewayAuth(params) {
|
|
|
6101
6359
|
const configPath = params.stateDir ? `${params.stateDir}/openclaw.json` : void 0;
|
|
6102
6360
|
if (configPath) {
|
|
6103
6361
|
try {
|
|
6104
|
-
const configData = JSON.parse(
|
|
6362
|
+
const configData = JSON.parse(readFileSync13(configPath, "utf-8"));
|
|
6105
6363
|
const rawGatewayAuthMode = trimToUndefined(configData?.gateway?.auth?.mode);
|
|
6106
6364
|
if (rawGatewayAuthMode === "token" || rawGatewayAuthMode === "password") {
|
|
6107
6365
|
configGatewayAuthMode = rawGatewayAuthMode;
|
|
@@ -6128,6 +6386,7 @@ var index_default = {
|
|
|
6128
6386
|
register(api) {
|
|
6129
6387
|
const config = api.pluginConfig ?? {};
|
|
6130
6388
|
let storage = null;
|
|
6389
|
+
let appNameMapProvider = null;
|
|
6131
6390
|
const ignoredApps = new Set(config.ignoredApps ?? []);
|
|
6132
6391
|
const openclawDir = api.runtime.state.resolveStateDir();
|
|
6133
6392
|
const logger = openclawDir ? new PluginFileLogger(api.logger, openclawDir) : api.logger;
|
|
@@ -6138,17 +6397,25 @@ var index_default = {
|
|
|
6138
6397
|
id: "notification-storage",
|
|
6139
6398
|
async start(ctx) {
|
|
6140
6399
|
const storageDir = resolveNotificationStorageDir(ctx, logger);
|
|
6141
|
-
|
|
6400
|
+
appNameMapProvider = createAppNameMapProvider({
|
|
6401
|
+
stateDir: ctx.stateDir,
|
|
6402
|
+
logger
|
|
6403
|
+
});
|
|
6404
|
+
await appNameMapProvider.start();
|
|
6405
|
+
const resolveDisplayName = appNameMapProvider.resolveDisplayName.bind(appNameMapProvider);
|
|
6406
|
+
storage = new NotificationStorage(storageDir, config, logger, resolveDisplayName);
|
|
6142
6407
|
await storage.init();
|
|
6143
6408
|
logger.info(`\u901A\u77E5\u5B58\u50A8\u670D\u52A1\u5DF2\u542F\u52A8: ${storageDir}`);
|
|
6144
6409
|
},
|
|
6145
6410
|
async stop() {
|
|
6146
6411
|
await storage?.close();
|
|
6147
6412
|
storage = null;
|
|
6413
|
+
appNameMapProvider?.stop();
|
|
6414
|
+
appNameMapProvider = null;
|
|
6148
6415
|
logger.info("\u901A\u77E5\u5B58\u50A8\u670D\u52A1\u5DF2\u505C\u6B62");
|
|
6149
6416
|
}
|
|
6150
6417
|
});
|
|
6151
|
-
const tunnelUrl =
|
|
6418
|
+
const tunnelUrl = getEnvUrls().relayTunnelUrl;
|
|
6152
6419
|
if (tunnelUrl) {
|
|
6153
6420
|
const gatewayPort = process.env.OPENCLAW_GATEWAY_PORT || "18789";
|
|
6154
6421
|
const { gatewayAuthMode, gatewayToken, gatewayPassword } = resolveLocalGatewayAuth({
|
|
@@ -6307,9 +6574,9 @@ var index_default = {
|
|
|
6307
6574
|
}
|
|
6308
6575
|
},
|
|
6309
6576
|
async execute(_toolCallId, params) {
|
|
6310
|
-
let
|
|
6577
|
+
let apiKey;
|
|
6311
6578
|
try {
|
|
6312
|
-
|
|
6579
|
+
apiKey = requireApiKey();
|
|
6313
6580
|
} catch (e) {
|
|
6314
6581
|
return {
|
|
6315
6582
|
ok: false,
|
|
@@ -6317,7 +6584,7 @@ var index_default = {
|
|
|
6317
6584
|
};
|
|
6318
6585
|
}
|
|
6319
6586
|
const { segments, repeat } = params;
|
|
6320
|
-
const result = await sendLightEffect(
|
|
6587
|
+
const result = await sendLightEffect(apiKey, segments, logger, repeat);
|
|
6321
6588
|
if (!result.ok) {
|
|
6322
6589
|
logger.warn(
|
|
6323
6590
|
`Light control HTTP request failed: ${result.status} ${result.error}`
|
|
@@ -6373,8 +6640,8 @@ var index_default = {
|
|
|
6373
6640
|
const inferOpenClawRootDir = (workspaceDir) => {
|
|
6374
6641
|
if (openclawDir) return openclawDir;
|
|
6375
6642
|
if (!workspaceDir) return void 0;
|
|
6376
|
-
if (
|
|
6377
|
-
return
|
|
6643
|
+
if (basename2(workspaceDir) !== "workspace") return void 0;
|
|
6644
|
+
return dirname5(workspaceDir);
|
|
6378
6645
|
};
|
|
6379
6646
|
api.registerCli(
|
|
6380
6647
|
(ctx) => {
|