codeksei 0.1.0 → 0.1.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/LICENSE +661 -661
- package/README.en.md +109 -47
- package/README.md +79 -58
- package/bin/cyberboss.js +1 -1
- package/package.json +86 -86
- package/scripts/open_shared_wechat_thread.sh +77 -77
- package/scripts/open_wechat_thread.sh +108 -108
- package/scripts/shared-common.js +144 -144
- package/scripts/shared-open.js +14 -14
- package/scripts/shared-start.js +5 -5
- package/scripts/shared-status.js +27 -27
- package/scripts/show_shared_status.sh +45 -45
- package/scripts/start_shared_app_server.sh +52 -52
- package/scripts/start_shared_wechat.sh +94 -94
- package/scripts/timeline-screenshot.sh +14 -14
- package/src/adapters/channel/weixin/account-store.js +99 -99
- package/src/adapters/channel/weixin/api-v2.js +50 -50
- package/src/adapters/channel/weixin/api.js +169 -169
- package/src/adapters/channel/weixin/context-token-store.js +84 -84
- package/src/adapters/channel/weixin/index.js +618 -604
- package/src/adapters/channel/weixin/legacy.js +579 -566
- package/src/adapters/channel/weixin/media-mime.js +22 -22
- package/src/adapters/channel/weixin/media-receive.js +370 -370
- package/src/adapters/channel/weixin/media-send.js +102 -102
- package/src/adapters/channel/weixin/message-utils-v2.js +282 -282
- package/src/adapters/channel/weixin/message-utils.js +199 -199
- package/src/adapters/channel/weixin/redact.js +41 -41
- package/src/adapters/channel/weixin/reminder-queue-store.js +101 -101
- package/src/adapters/channel/weixin/sync-buffer-store.js +35 -35
- package/src/adapters/runtime/codex/events.js +215 -215
- package/src/adapters/runtime/codex/index.js +109 -104
- package/src/adapters/runtime/codex/message-utils.js +95 -95
- package/src/adapters/runtime/codex/model-catalog.js +106 -106
- package/src/adapters/runtime/codex/protocol-leak-monitor.js +75 -75
- package/src/adapters/runtime/codex/rpc-client.js +339 -339
- package/src/adapters/runtime/codex/session-store.js +286 -286
- package/src/app/channel-send-file-cli.js +57 -57
- package/src/app/diary-write-cli.js +236 -88
- package/src/app/note-sync-cli.js +2 -2
- package/src/app/reminder-write-cli.js +215 -210
- package/src/app/review-cli.js +7 -5
- package/src/app/system-checkin-poller.js +64 -64
- package/src/app/system-send-cli.js +129 -129
- package/src/app/timeline-event-cli.js +28 -25
- package/src/app/timeline-screenshot-cli.js +103 -100
- package/src/core/app.js +1763 -1763
- package/src/core/branding.js +2 -1
- package/src/core/command-registry.js +381 -369
- package/src/core/config.js +30 -14
- package/src/core/default-targets.js +163 -163
- package/src/core/durable-note-schema.js +9 -8
- package/src/core/instructions-template.js +17 -16
- package/src/core/note-sync.js +8 -7
- package/src/core/path-utils.js +54 -0
- package/src/core/project-radar.js +11 -10
- package/src/core/review.js +48 -50
- package/src/core/stream-delivery.js +1162 -983
- package/src/core/system-message-dispatcher.js +68 -68
- package/src/core/system-message-queue-store.js +128 -128
- package/src/core/thread-state-store.js +96 -96
- package/src/core/timeline-screenshot-queue-store.js +134 -134
- package/src/core/timezone.js +436 -0
- package/src/core/workspace-bootstrap.js +9 -1
- package/src/index.js +148 -146
- package/src/integrations/timeline/index.js +130 -74
- package/src/integrations/timeline/state-sync.js +240 -0
- package/templates/weixin-instructions.md +12 -38
- package/templates/weixin-operations.md +29 -31
|
@@ -2,30 +2,30 @@ const fs = require("fs");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { redactSensitiveText } = require("./redact");
|
|
4
4
|
const { getStableWechatUin } = require("./protocol");
|
|
5
|
-
|
|
6
|
-
function readChannelVersion() {
|
|
7
|
-
try {
|
|
8
|
-
const pkgPath = path.resolve(__dirname, "../../../../package.json");
|
|
9
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
10
|
-
return pkg.version || "unknown";
|
|
11
|
-
} catch {
|
|
12
|
-
return "unknown";
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const CHANNEL_VERSION = readChannelVersion();
|
|
17
|
-
const DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;
|
|
18
|
-
const DEFAULT_API_TIMEOUT_MS = 15_000;
|
|
19
|
-
const DEFAULT_CONFIG_TIMEOUT_MS = 10_000;
|
|
20
|
-
|
|
21
|
-
function buildBaseInfo() {
|
|
22
|
-
return { channel_version: CHANNEL_VERSION };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function ensureTrailingSlash(url) {
|
|
26
|
-
return url.endsWith("/") ? url : `${url}/`;
|
|
27
|
-
}
|
|
28
|
-
|
|
5
|
+
|
|
6
|
+
function readChannelVersion() {
|
|
7
|
+
try {
|
|
8
|
+
const pkgPath = path.resolve(__dirname, "../../../../package.json");
|
|
9
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
10
|
+
return pkg.version || "unknown";
|
|
11
|
+
} catch {
|
|
12
|
+
return "unknown";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const CHANNEL_VERSION = readChannelVersion();
|
|
17
|
+
const DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;
|
|
18
|
+
const DEFAULT_API_TIMEOUT_MS = 15_000;
|
|
19
|
+
const DEFAULT_CONFIG_TIMEOUT_MS = 10_000;
|
|
20
|
+
|
|
21
|
+
function buildBaseInfo() {
|
|
22
|
+
return { channel_version: CHANNEL_VERSION };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function ensureTrailingSlash(url) {
|
|
26
|
+
return url.endsWith("/") ? url : `${url}/`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
29
|
function buildHeaders(opts) {
|
|
30
30
|
const headers = {
|
|
31
31
|
"Content-Type": "application/json",
|
|
@@ -33,148 +33,148 @@ function buildHeaders(opts) {
|
|
|
33
33
|
"Content-Length": String(Buffer.byteLength(opts.body, "utf8")),
|
|
34
34
|
"X-WECHAT-UIN": getStableWechatUin(),
|
|
35
35
|
};
|
|
36
|
-
if (opts.token && String(opts.token).trim()) {
|
|
37
|
-
headers.Authorization = `Bearer ${String(opts.token).trim()}`;
|
|
38
|
-
}
|
|
39
|
-
return headers;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function apiFetch(params) {
|
|
43
|
-
const base = ensureTrailingSlash(params.baseUrl);
|
|
44
|
-
const url = new URL(params.endpoint, base);
|
|
45
|
-
const headers = buildHeaders({ token: params.token, body: params.body });
|
|
46
|
-
const controller = new AbortController();
|
|
47
|
-
const timer = setTimeout(() => controller.abort(), params.timeoutMs);
|
|
48
|
-
try {
|
|
49
|
-
const response = await fetch(url.toString(), {
|
|
50
|
-
method: "POST",
|
|
51
|
-
headers,
|
|
52
|
-
body: params.body,
|
|
53
|
-
signal: controller.signal,
|
|
54
|
-
});
|
|
55
|
-
clearTimeout(timer);
|
|
56
|
-
const rawText = await response.text();
|
|
57
|
-
if (!response.ok) {
|
|
58
|
-
throw new Error(`${params.label} ${response.status}: ${redactSensitiveText(rawText)}`);
|
|
59
|
-
}
|
|
60
|
-
return rawText;
|
|
61
|
-
} catch (error) {
|
|
62
|
-
clearTimeout(timer);
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function parseApiJson(rawText, label) {
|
|
68
|
-
try {
|
|
69
|
-
return JSON.parse(rawText);
|
|
70
|
-
} catch {
|
|
71
|
-
throw new Error(`${label} returned invalid JSON: ${redactSensitiveText(rawText)}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function assertApiSuccess(response, label) {
|
|
76
|
-
const ret = response?.ret;
|
|
77
|
-
const errcode = response?.errcode;
|
|
78
|
-
if ((ret !== undefined && ret !== 0) || (errcode !== undefined && errcode !== 0)) {
|
|
79
|
-
const errmsg = typeof response?.errmsg === "string" ? response.errmsg.trim() : "";
|
|
80
|
-
throw new Error(`${label} ret=${ret ?? ""} errcode=${errcode ?? ""} errmsg=${redactSensitiveText(errmsg)}`);
|
|
81
|
-
}
|
|
82
|
-
return response;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function getUpdates(params) {
|
|
86
|
-
const timeout = params.timeoutMs || DEFAULT_LONG_POLL_TIMEOUT_MS;
|
|
87
|
-
try {
|
|
88
|
-
const rawText = await apiFetch({
|
|
89
|
-
baseUrl: params.baseUrl,
|
|
90
|
-
endpoint: "ilink/bot/getupdates",
|
|
91
|
-
body: JSON.stringify({
|
|
92
|
-
get_updates_buf: params.get_updates_buf || "",
|
|
93
|
-
base_info: buildBaseInfo(),
|
|
94
|
-
}),
|
|
95
|
-
token: params.token,
|
|
96
|
-
timeoutMs: timeout,
|
|
97
|
-
label: "getUpdates",
|
|
98
|
-
});
|
|
99
|
-
return parseApiJson(rawText, "getUpdates");
|
|
100
|
-
} catch (error) {
|
|
101
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
102
|
-
return { ret: 0, msgs: [], get_updates_buf: params.get_updates_buf || "" };
|
|
103
|
-
}
|
|
104
|
-
throw error;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function sendMessage(params) {
|
|
109
|
-
const rawText = await apiFetch({
|
|
110
|
-
baseUrl: params.baseUrl,
|
|
111
|
-
endpoint: "ilink/bot/sendmessage",
|
|
112
|
-
body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),
|
|
113
|
-
token: params.token,
|
|
114
|
-
timeoutMs: params.timeoutMs || DEFAULT_API_TIMEOUT_MS,
|
|
115
|
-
label: "sendMessage",
|
|
116
|
-
});
|
|
117
|
-
assertApiSuccess(parseApiJson(rawText, "sendMessage"), "sendMessage");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function getUploadUrl(params) {
|
|
121
|
-
const rawText = await apiFetch({
|
|
122
|
-
baseUrl: params.baseUrl,
|
|
123
|
-
endpoint: "ilink/bot/getuploadurl",
|
|
124
|
-
body: JSON.stringify({
|
|
125
|
-
filekey: params.filekey,
|
|
126
|
-
media_type: params.media_type,
|
|
127
|
-
to_user_id: params.to_user_id,
|
|
128
|
-
rawsize: params.rawsize,
|
|
129
|
-
rawfilemd5: params.rawfilemd5,
|
|
130
|
-
filesize: params.filesize,
|
|
131
|
-
thumb_rawsize: params.thumb_rawsize,
|
|
132
|
-
thumb_rawfilemd5: params.thumb_rawfilemd5,
|
|
133
|
-
thumb_filesize: params.thumb_filesize,
|
|
134
|
-
no_need_thumb: params.no_need_thumb,
|
|
135
|
-
aeskey: params.aeskey,
|
|
136
|
-
base_info: buildBaseInfo(),
|
|
137
|
-
}),
|
|
138
|
-
token: params.token,
|
|
139
|
-
timeoutMs: params.timeoutMs || DEFAULT_API_TIMEOUT_MS,
|
|
140
|
-
label: "getUploadUrl",
|
|
141
|
-
});
|
|
142
|
-
return assertApiSuccess(parseApiJson(rawText, "getUploadUrl"), "getUploadUrl");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function getConfig(params) {
|
|
146
|
-
const rawText = await apiFetch({
|
|
147
|
-
baseUrl: params.baseUrl,
|
|
148
|
-
endpoint: "ilink/bot/getconfig",
|
|
149
|
-
body: JSON.stringify({
|
|
150
|
-
ilink_user_id: params.ilinkUserId,
|
|
151
|
-
context_token: params.contextToken,
|
|
152
|
-
base_info: buildBaseInfo(),
|
|
153
|
-
}),
|
|
154
|
-
token: params.token,
|
|
155
|
-
timeoutMs: params.timeoutMs || DEFAULT_CONFIG_TIMEOUT_MS,
|
|
156
|
-
label: "getConfig",
|
|
157
|
-
});
|
|
158
|
-
return assertApiSuccess(parseApiJson(rawText, "getConfig"), "getConfig");
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function sendTyping(params) {
|
|
162
|
-
const rawText = await apiFetch({
|
|
163
|
-
baseUrl: params.baseUrl,
|
|
164
|
-
endpoint: "ilink/bot/sendtyping",
|
|
165
|
-
body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),
|
|
166
|
-
token: params.token,
|
|
167
|
-
timeoutMs: params.timeoutMs || DEFAULT_CONFIG_TIMEOUT_MS,
|
|
168
|
-
label: "sendTyping",
|
|
169
|
-
});
|
|
170
|
-
assertApiSuccess(parseApiJson(rawText, "sendTyping"), "sendTyping");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
module.exports = {
|
|
174
|
-
buildBaseInfo,
|
|
175
|
-
getConfig,
|
|
176
|
-
getUploadUrl,
|
|
177
|
-
getUpdates,
|
|
178
|
-
sendMessage,
|
|
179
|
-
sendTyping,
|
|
180
|
-
};
|
|
36
|
+
if (opts.token && String(opts.token).trim()) {
|
|
37
|
+
headers.Authorization = `Bearer ${String(opts.token).trim()}`;
|
|
38
|
+
}
|
|
39
|
+
return headers;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function apiFetch(params) {
|
|
43
|
+
const base = ensureTrailingSlash(params.baseUrl);
|
|
44
|
+
const url = new URL(params.endpoint, base);
|
|
45
|
+
const headers = buildHeaders({ token: params.token, body: params.body });
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timer = setTimeout(() => controller.abort(), params.timeoutMs);
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(url.toString(), {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers,
|
|
52
|
+
body: params.body,
|
|
53
|
+
signal: controller.signal,
|
|
54
|
+
});
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
const rawText = await response.text();
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(`${params.label} ${response.status}: ${redactSensitiveText(rawText)}`);
|
|
59
|
+
}
|
|
60
|
+
return rawText;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseApiJson(rawText, label) {
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(rawText);
|
|
70
|
+
} catch {
|
|
71
|
+
throw new Error(`${label} returned invalid JSON: ${redactSensitiveText(rawText)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function assertApiSuccess(response, label) {
|
|
76
|
+
const ret = response?.ret;
|
|
77
|
+
const errcode = response?.errcode;
|
|
78
|
+
if ((ret !== undefined && ret !== 0) || (errcode !== undefined && errcode !== 0)) {
|
|
79
|
+
const errmsg = typeof response?.errmsg === "string" ? response.errmsg.trim() : "";
|
|
80
|
+
throw new Error(`${label} ret=${ret ?? ""} errcode=${errcode ?? ""} errmsg=${redactSensitiveText(errmsg)}`);
|
|
81
|
+
}
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function getUpdates(params) {
|
|
86
|
+
const timeout = params.timeoutMs || DEFAULT_LONG_POLL_TIMEOUT_MS;
|
|
87
|
+
try {
|
|
88
|
+
const rawText = await apiFetch({
|
|
89
|
+
baseUrl: params.baseUrl,
|
|
90
|
+
endpoint: "ilink/bot/getupdates",
|
|
91
|
+
body: JSON.stringify({
|
|
92
|
+
get_updates_buf: params.get_updates_buf || "",
|
|
93
|
+
base_info: buildBaseInfo(),
|
|
94
|
+
}),
|
|
95
|
+
token: params.token,
|
|
96
|
+
timeoutMs: timeout,
|
|
97
|
+
label: "getUpdates",
|
|
98
|
+
});
|
|
99
|
+
return parseApiJson(rawText, "getUpdates");
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
102
|
+
return { ret: 0, msgs: [], get_updates_buf: params.get_updates_buf || "" };
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function sendMessage(params) {
|
|
109
|
+
const rawText = await apiFetch({
|
|
110
|
+
baseUrl: params.baseUrl,
|
|
111
|
+
endpoint: "ilink/bot/sendmessage",
|
|
112
|
+
body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),
|
|
113
|
+
token: params.token,
|
|
114
|
+
timeoutMs: params.timeoutMs || DEFAULT_API_TIMEOUT_MS,
|
|
115
|
+
label: "sendMessage",
|
|
116
|
+
});
|
|
117
|
+
assertApiSuccess(parseApiJson(rawText, "sendMessage"), "sendMessage");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function getUploadUrl(params) {
|
|
121
|
+
const rawText = await apiFetch({
|
|
122
|
+
baseUrl: params.baseUrl,
|
|
123
|
+
endpoint: "ilink/bot/getuploadurl",
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
filekey: params.filekey,
|
|
126
|
+
media_type: params.media_type,
|
|
127
|
+
to_user_id: params.to_user_id,
|
|
128
|
+
rawsize: params.rawsize,
|
|
129
|
+
rawfilemd5: params.rawfilemd5,
|
|
130
|
+
filesize: params.filesize,
|
|
131
|
+
thumb_rawsize: params.thumb_rawsize,
|
|
132
|
+
thumb_rawfilemd5: params.thumb_rawfilemd5,
|
|
133
|
+
thumb_filesize: params.thumb_filesize,
|
|
134
|
+
no_need_thumb: params.no_need_thumb,
|
|
135
|
+
aeskey: params.aeskey,
|
|
136
|
+
base_info: buildBaseInfo(),
|
|
137
|
+
}),
|
|
138
|
+
token: params.token,
|
|
139
|
+
timeoutMs: params.timeoutMs || DEFAULT_API_TIMEOUT_MS,
|
|
140
|
+
label: "getUploadUrl",
|
|
141
|
+
});
|
|
142
|
+
return assertApiSuccess(parseApiJson(rawText, "getUploadUrl"), "getUploadUrl");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function getConfig(params) {
|
|
146
|
+
const rawText = await apiFetch({
|
|
147
|
+
baseUrl: params.baseUrl,
|
|
148
|
+
endpoint: "ilink/bot/getconfig",
|
|
149
|
+
body: JSON.stringify({
|
|
150
|
+
ilink_user_id: params.ilinkUserId,
|
|
151
|
+
context_token: params.contextToken,
|
|
152
|
+
base_info: buildBaseInfo(),
|
|
153
|
+
}),
|
|
154
|
+
token: params.token,
|
|
155
|
+
timeoutMs: params.timeoutMs || DEFAULT_CONFIG_TIMEOUT_MS,
|
|
156
|
+
label: "getConfig",
|
|
157
|
+
});
|
|
158
|
+
return assertApiSuccess(parseApiJson(rawText, "getConfig"), "getConfig");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function sendTyping(params) {
|
|
162
|
+
const rawText = await apiFetch({
|
|
163
|
+
baseUrl: params.baseUrl,
|
|
164
|
+
endpoint: "ilink/bot/sendtyping",
|
|
165
|
+
body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),
|
|
166
|
+
token: params.token,
|
|
167
|
+
timeoutMs: params.timeoutMs || DEFAULT_CONFIG_TIMEOUT_MS,
|
|
168
|
+
label: "sendTyping",
|
|
169
|
+
});
|
|
170
|
+
assertApiSuccess(parseApiJson(rawText, "sendTyping"), "sendTyping");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
buildBaseInfo,
|
|
175
|
+
getConfig,
|
|
176
|
+
getUploadUrl,
|
|
177
|
+
getUpdates,
|
|
178
|
+
sendMessage,
|
|
179
|
+
sendTyping,
|
|
180
|
+
};
|
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const { normalizeAccountId } = require("./account-store");
|
|
4
|
-
|
|
5
|
-
function ensureAccountsDir(config) {
|
|
6
|
-
fs.mkdirSync(config.accountsDir, { recursive: true });
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function resolveContextTokenPath(config, accountId) {
|
|
10
|
-
ensureAccountsDir(config);
|
|
11
|
-
return path.join(config.accountsDir, `${normalizeAccountId(accountId)}.context-tokens.json`);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function loadPersistedContextTokens(config, accountId) {
|
|
15
|
-
try {
|
|
16
|
-
const filePath = resolveContextTokenPath(config, accountId);
|
|
17
|
-
if (!fs.existsSync(filePath)) {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
const raw = fs.readFileSync(filePath, "utf8");
|
|
21
|
-
const parsed = JSON.parse(raw);
|
|
22
|
-
if (!parsed || typeof parsed !== "object") {
|
|
23
|
-
return {};
|
|
24
|
-
}
|
|
25
|
-
return Object.fromEntries(
|
|
26
|
-
Object.entries(parsed)
|
|
27
|
-
.filter(([userId, token]) => typeof userId === "string" && userId.trim() && typeof token === "string" && token.trim())
|
|
28
|
-
.map(([userId, token]) => [userId.trim(), token.trim()])
|
|
29
|
-
);
|
|
30
|
-
} catch {
|
|
31
|
-
return {};
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function savePersistedContextTokens(config, accountId, tokens) {
|
|
36
|
-
const normalizedTokens = Object.fromEntries(
|
|
37
|
-
Object.entries(tokens || {})
|
|
38
|
-
.filter(([userId, token]) => typeof userId === "string" && userId.trim() && typeof token === "string" && token.trim())
|
|
39
|
-
.map(([userId, token]) => [userId.trim(), token.trim()])
|
|
40
|
-
);
|
|
41
|
-
const filePath = resolveContextTokenPath(config, accountId);
|
|
42
|
-
fs.writeFileSync(filePath, JSON.stringify(normalizedTokens, null, 2), "utf8");
|
|
43
|
-
try {
|
|
44
|
-
fs.chmodSync(filePath, 0o600);
|
|
45
|
-
} catch {
|
|
46
|
-
// best effort
|
|
47
|
-
}
|
|
48
|
-
return normalizedTokens;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function persistContextToken(config, accountId, userId, token) {
|
|
52
|
-
const normalizedUserId = typeof userId === "string" ? userId.trim() : "";
|
|
53
|
-
const normalizedToken = typeof token === "string" ? token.trim() : "";
|
|
54
|
-
if (!normalizedUserId || !normalizedToken) {
|
|
55
|
-
return loadPersistedContextTokens(config, accountId);
|
|
56
|
-
}
|
|
57
|
-
const existing = loadPersistedContextTokens(config, accountId);
|
|
58
|
-
if (existing[normalizedUserId] === normalizedToken) {
|
|
59
|
-
return existing;
|
|
60
|
-
}
|
|
61
|
-
return savePersistedContextTokens(config, accountId, {
|
|
62
|
-
...existing,
|
|
63
|
-
[normalizedUserId]: normalizedToken,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function clearPersistedContextTokens(config, accountId) {
|
|
68
|
-
try {
|
|
69
|
-
const filePath = resolveContextTokenPath(config, accountId);
|
|
70
|
-
if (fs.existsSync(filePath)) {
|
|
71
|
-
fs.unlinkSync(filePath);
|
|
72
|
-
}
|
|
73
|
-
} catch {
|
|
74
|
-
// best effort
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
module.exports = {
|
|
79
|
-
clearPersistedContextTokens,
|
|
80
|
-
loadPersistedContextTokens,
|
|
81
|
-
persistContextToken,
|
|
82
|
-
resolveContextTokenPath,
|
|
83
|
-
};
|
|
84
|
-
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { normalizeAccountId } = require("./account-store");
|
|
4
|
+
|
|
5
|
+
function ensureAccountsDir(config) {
|
|
6
|
+
fs.mkdirSync(config.accountsDir, { recursive: true });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function resolveContextTokenPath(config, accountId) {
|
|
10
|
+
ensureAccountsDir(config);
|
|
11
|
+
return path.join(config.accountsDir, `${normalizeAccountId(accountId)}.context-tokens.json`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadPersistedContextTokens(config, accountId) {
|
|
15
|
+
try {
|
|
16
|
+
const filePath = resolveContextTokenPath(config, accountId);
|
|
17
|
+
if (!fs.existsSync(filePath)) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
if (!parsed || typeof parsed !== "object") {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
return Object.fromEntries(
|
|
26
|
+
Object.entries(parsed)
|
|
27
|
+
.filter(([userId, token]) => typeof userId === "string" && userId.trim() && typeof token === "string" && token.trim())
|
|
28
|
+
.map(([userId, token]) => [userId.trim(), token.trim()])
|
|
29
|
+
);
|
|
30
|
+
} catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function savePersistedContextTokens(config, accountId, tokens) {
|
|
36
|
+
const normalizedTokens = Object.fromEntries(
|
|
37
|
+
Object.entries(tokens || {})
|
|
38
|
+
.filter(([userId, token]) => typeof userId === "string" && userId.trim() && typeof token === "string" && token.trim())
|
|
39
|
+
.map(([userId, token]) => [userId.trim(), token.trim()])
|
|
40
|
+
);
|
|
41
|
+
const filePath = resolveContextTokenPath(config, accountId);
|
|
42
|
+
fs.writeFileSync(filePath, JSON.stringify(normalizedTokens, null, 2), "utf8");
|
|
43
|
+
try {
|
|
44
|
+
fs.chmodSync(filePath, 0o600);
|
|
45
|
+
} catch {
|
|
46
|
+
// best effort
|
|
47
|
+
}
|
|
48
|
+
return normalizedTokens;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function persistContextToken(config, accountId, userId, token) {
|
|
52
|
+
const normalizedUserId = typeof userId === "string" ? userId.trim() : "";
|
|
53
|
+
const normalizedToken = typeof token === "string" ? token.trim() : "";
|
|
54
|
+
if (!normalizedUserId || !normalizedToken) {
|
|
55
|
+
return loadPersistedContextTokens(config, accountId);
|
|
56
|
+
}
|
|
57
|
+
const existing = loadPersistedContextTokens(config, accountId);
|
|
58
|
+
if (existing[normalizedUserId] === normalizedToken) {
|
|
59
|
+
return existing;
|
|
60
|
+
}
|
|
61
|
+
return savePersistedContextTokens(config, accountId, {
|
|
62
|
+
...existing,
|
|
63
|
+
[normalizedUserId]: normalizedToken,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function clearPersistedContextTokens(config, accountId) {
|
|
68
|
+
try {
|
|
69
|
+
const filePath = resolveContextTokenPath(config, accountId);
|
|
70
|
+
if (fs.existsSync(filePath)) {
|
|
71
|
+
fs.unlinkSync(filePath);
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// best effort
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
clearPersistedContextTokens,
|
|
80
|
+
loadPersistedContextTokens,
|
|
81
|
+
persistContextToken,
|
|
82
|
+
resolveContextTokenPath,
|
|
83
|
+
};
|
|
84
|
+
|