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
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#!/bin/zsh
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
1
|
+
#!/bin/zsh
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
5
5
|
PORT="${CODEKSEI_SHARED_PORT:-${CYBERBOSS_SHARED_PORT:-8765}}"
|
|
6
6
|
STATE_DIR="${CODEKSEI_STATE_DIR:-${CYBERBOSS_STATE_DIR:-$HOME/.codeksei}}"
|
|
7
7
|
if [[ ! -d "${STATE_DIR}" && -d "$HOME/.cyberboss" ]]; then
|
|
@@ -9,100 +9,100 @@ if [[ ! -d "${STATE_DIR}" && -d "$HOME/.cyberboss" ]]; then
|
|
|
9
9
|
fi
|
|
10
10
|
LOG_DIR="${STATE_DIR}/logs"
|
|
11
11
|
PID_FILE="${LOG_DIR}/shared-wechat.pid"
|
|
12
|
-
|
|
13
|
-
function resolve_pid_cwd() {
|
|
14
|
-
local pid="$1"
|
|
15
|
-
lsof -a -p "${pid}" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -n 1
|
|
16
|
-
}
|
|
17
|
-
|
|
12
|
+
|
|
13
|
+
function resolve_pid_cwd() {
|
|
14
|
+
local pid="$1"
|
|
15
|
+
lsof -a -p "${pid}" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -n 1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
18
|
function list_bridge_processes() {
|
|
19
19
|
ps -ax -o pid=,ppid=,command= | awk '/node \.\/bin\/(codeksei|cyberboss)\.js start --checkin/ { print }'
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
function find_bridge_child_pid() {
|
|
23
|
-
local parent_pid="$1"
|
|
24
|
-
list_bridge_processes | awk -v target_ppid="${parent_pid}" '$2 == target_ppid { print $1; exit }'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function resolve_bridge_pid() {
|
|
28
|
-
local candidate_pid="$1"
|
|
29
|
-
[[ -n "${candidate_pid}" ]] || return 1
|
|
30
|
-
if ! kill -0 "${candidate_pid}" 2>/dev/null; then
|
|
31
|
-
return 1
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
local child_pid
|
|
35
|
-
child_pid="$(find_bridge_child_pid "${candidate_pid}")"
|
|
36
|
-
if [[ -n "${child_pid}" ]]; then
|
|
37
|
-
echo "${child_pid}"
|
|
38
|
-
return 0
|
|
39
|
-
fi
|
|
40
|
-
|
|
41
|
-
if [[ "$(resolve_pid_cwd "${candidate_pid}")" == "${ROOT_DIR}" ]]; then
|
|
42
|
-
echo "${candidate_pid}"
|
|
43
|
-
return 0
|
|
44
|
-
fi
|
|
45
|
-
|
|
46
|
-
return 1
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function find_existing_bridge_pid() {
|
|
50
|
-
if [[ -f "${PID_FILE}" ]]; then
|
|
51
|
-
local pid_from_file
|
|
52
|
-
pid_from_file="$(cat "${PID_FILE}" 2>/dev/null || true)"
|
|
53
|
-
local resolved_from_file
|
|
54
|
-
resolved_from_file="$(resolve_bridge_pid "${pid_from_file}" || true)"
|
|
55
|
-
if [[ -n "${resolved_from_file}" ]]; then
|
|
56
|
-
echo "${resolved_from_file}"
|
|
57
|
-
return 0
|
|
58
|
-
fi
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
local pid
|
|
62
|
-
while read -r pid _; do
|
|
63
|
-
[[ -n "${pid}" ]] || continue
|
|
64
|
-
if [[ "$(resolve_pid_cwd "${pid}")" == "${ROOT_DIR}" ]]; then
|
|
65
|
-
echo "${pid}"
|
|
66
|
-
return 0
|
|
67
|
-
fi
|
|
68
|
-
done < <(list_bridge_processes)
|
|
69
|
-
|
|
70
|
-
return 1
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function cleanup_pid_file() {
|
|
74
|
-
if [[ -f "${PID_FILE}" ]]; then
|
|
75
|
-
local current_pid
|
|
76
|
-
current_pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
|
|
77
|
-
if [[ "${current_pid}" == "$$" ]]; then
|
|
78
|
-
rm -f "${PID_FILE}"
|
|
79
|
-
fi
|
|
80
|
-
fi
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
"${ROOT_DIR}/scripts/start_shared_app_server.sh"
|
|
84
|
-
mkdir -p "${LOG_DIR}"
|
|
85
|
-
|
|
86
|
-
EXISTING_PID="$(find_existing_bridge_pid || true)"
|
|
87
|
-
if [[ -n "${EXISTING_PID}" ]]; then
|
|
88
|
-
echo "${EXISTING_PID}" > "${PID_FILE}"
|
|
21
|
+
|
|
22
|
+
function find_bridge_child_pid() {
|
|
23
|
+
local parent_pid="$1"
|
|
24
|
+
list_bridge_processes | awk -v target_ppid="${parent_pid}" '$2 == target_ppid { print $1; exit }'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolve_bridge_pid() {
|
|
28
|
+
local candidate_pid="$1"
|
|
29
|
+
[[ -n "${candidate_pid}" ]] || return 1
|
|
30
|
+
if ! kill -0 "${candidate_pid}" 2>/dev/null; then
|
|
31
|
+
return 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
local child_pid
|
|
35
|
+
child_pid="$(find_bridge_child_pid "${candidate_pid}")"
|
|
36
|
+
if [[ -n "${child_pid}" ]]; then
|
|
37
|
+
echo "${child_pid}"
|
|
38
|
+
return 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [[ "$(resolve_pid_cwd "${candidate_pid}")" == "${ROOT_DIR}" ]]; then
|
|
42
|
+
echo "${candidate_pid}"
|
|
43
|
+
return 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
return 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function find_existing_bridge_pid() {
|
|
50
|
+
if [[ -f "${PID_FILE}" ]]; then
|
|
51
|
+
local pid_from_file
|
|
52
|
+
pid_from_file="$(cat "${PID_FILE}" 2>/dev/null || true)"
|
|
53
|
+
local resolved_from_file
|
|
54
|
+
resolved_from_file="$(resolve_bridge_pid "${pid_from_file}" || true)"
|
|
55
|
+
if [[ -n "${resolved_from_file}" ]]; then
|
|
56
|
+
echo "${resolved_from_file}"
|
|
57
|
+
return 0
|
|
58
|
+
fi
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
local pid
|
|
62
|
+
while read -r pid _; do
|
|
63
|
+
[[ -n "${pid}" ]] || continue
|
|
64
|
+
if [[ "$(resolve_pid_cwd "${pid}")" == "${ROOT_DIR}" ]]; then
|
|
65
|
+
echo "${pid}"
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
done < <(list_bridge_processes)
|
|
69
|
+
|
|
70
|
+
return 1
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function cleanup_pid_file() {
|
|
74
|
+
if [[ -f "${PID_FILE}" ]]; then
|
|
75
|
+
local current_pid
|
|
76
|
+
current_pid="$(cat "${PID_FILE}" 2>/dev/null || true)"
|
|
77
|
+
if [[ "${current_pid}" == "$$" ]]; then
|
|
78
|
+
rm -f "${PID_FILE}"
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
"${ROOT_DIR}/scripts/start_shared_app_server.sh"
|
|
84
|
+
mkdir -p "${LOG_DIR}"
|
|
85
|
+
|
|
86
|
+
EXISTING_PID="$(find_existing_bridge_pid || true)"
|
|
87
|
+
if [[ -n "${EXISTING_PID}" ]]; then
|
|
88
|
+
echo "${EXISTING_PID}" > "${PID_FILE}"
|
|
89
89
|
echo "shared codeksei already running pid=${EXISTING_PID}"
|
|
90
|
-
exit 0
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
BRIDGE_PID=""
|
|
94
|
-
function shutdown_bridge() {
|
|
95
|
-
if [[ -n "${BRIDGE_PID}" ]] && kill -0 "${BRIDGE_PID}" 2>/dev/null; then
|
|
96
|
-
kill "${BRIDGE_PID}" 2>/dev/null || true
|
|
97
|
-
fi
|
|
98
|
-
cleanup_pid_file
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
trap shutdown_bridge EXIT INT TERM
|
|
102
|
-
cd "${ROOT_DIR}"
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
BRIDGE_PID=""
|
|
94
|
+
function shutdown_bridge() {
|
|
95
|
+
if [[ -n "${BRIDGE_PID}" ]] && kill -0 "${BRIDGE_PID}" 2>/dev/null; then
|
|
96
|
+
kill "${BRIDGE_PID}" 2>/dev/null || true
|
|
97
|
+
fi
|
|
98
|
+
cleanup_pid_file
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
trap shutdown_bridge EXIT INT TERM
|
|
102
|
+
cd "${ROOT_DIR}"
|
|
103
103
|
export CODEKSEI_CODEX_ENDPOINT="ws://127.0.0.1:${PORT}"
|
|
104
104
|
export CYBERBOSS_CODEX_ENDPOINT="ws://127.0.0.1:${PORT}"
|
|
105
105
|
node ./bin/codeksei.js start --checkin &
|
|
106
|
-
BRIDGE_PID="$!"
|
|
107
|
-
echo "${BRIDGE_PID}" > "${PID_FILE}"
|
|
108
|
-
wait "${BRIDGE_PID}"
|
|
106
|
+
BRIDGE_PID="$!"
|
|
107
|
+
echo "${BRIDGE_PID}" > "${PID_FILE}"
|
|
108
|
+
wait "${BRIDGE_PID}"
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
5
|
-
ARGS=()
|
|
6
|
-
|
|
7
|
-
for arg in "$@"; do
|
|
8
|
-
if [[ "$arg" == "--send" ]]; then
|
|
9
|
-
continue
|
|
10
|
-
fi
|
|
11
|
-
ARGS+=("$arg")
|
|
12
|
-
done
|
|
13
|
-
|
|
14
|
-
cd "$ROOT"
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
5
|
+
ARGS=()
|
|
6
|
+
|
|
7
|
+
for arg in "$@"; do
|
|
8
|
+
if [[ "$arg" == "--send" ]]; then
|
|
9
|
+
continue
|
|
10
|
+
fi
|
|
11
|
+
ARGS+=("$arg")
|
|
12
|
+
done
|
|
13
|
+
|
|
14
|
+
cd "$ROOT"
|
|
15
15
|
exec node ./bin/codeksei.js timeline screenshot "${ARGS[@]}"
|
|
@@ -1,41 +1,41 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { normalizeRouteTag } = require("./protocol");
|
|
4
|
-
|
|
5
|
-
function normalizeAccountId(raw) {
|
|
6
|
-
return String(raw || "")
|
|
7
|
-
.trim()
|
|
8
|
-
.toLowerCase()
|
|
9
|
-
.replace(/[^a-z0-9._-]+/g, "-")
|
|
10
|
-
.replace(/-+/g, "-")
|
|
11
|
-
.replace(/^-|-$/g, "");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function ensureAccountsDir(config) {
|
|
15
|
-
fs.mkdirSync(config.accountsDir, { recursive: true });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function resolveAccountPath(config, accountId) {
|
|
19
|
-
return path.join(config.accountsDir, `${normalizeAccountId(accountId)}.json`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function deleteWeixinAccount(config, accountId) {
|
|
23
|
-
const normalized = normalizeAccountId(accountId);
|
|
24
|
-
if (!normalized) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
const filePath = resolveAccountPath(config, normalized);
|
|
29
|
-
if (!fs.existsSync(filePath)) {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
fs.unlinkSync(filePath);
|
|
33
|
-
return true;
|
|
34
|
-
} catch {
|
|
35
|
-
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
4
|
+
|
|
5
|
+
function normalizeAccountId(raw) {
|
|
6
|
+
return String(raw || "")
|
|
7
|
+
.trim()
|
|
8
|
+
.toLowerCase()
|
|
9
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
10
|
+
.replace(/-+/g, "-")
|
|
11
|
+
.replace(/^-|-$/g, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ensureAccountsDir(config) {
|
|
15
|
+
fs.mkdirSync(config.accountsDir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolveAccountPath(config, accountId) {
|
|
19
|
+
return path.join(config.accountsDir, `${normalizeAccountId(accountId)}.json`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function deleteWeixinAccount(config, accountId) {
|
|
23
|
+
const normalized = normalizeAccountId(accountId);
|
|
24
|
+
if (!normalized) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const filePath = resolveAccountPath(config, normalized);
|
|
29
|
+
if (!fs.existsSync(filePath)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
fs.unlinkSync(filePath);
|
|
33
|
+
return true;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
39
|
function saveWeixinAccount(config, rawAccountId, update) {
|
|
40
40
|
ensureAccountsDir(config);
|
|
41
41
|
const accountId = normalizeAccountId(rawAccountId);
|
|
@@ -53,26 +53,26 @@ function saveWeixinAccount(config, rawAccountId, update) {
|
|
|
53
53
|
: normalizeRouteTag(existing.routeTag || config.weixinRouteTag),
|
|
54
54
|
savedAt: new Date().toISOString(),
|
|
55
55
|
};
|
|
56
|
-
fs.writeFileSync(filePath, JSON.stringify(next, null, 2), "utf8");
|
|
57
|
-
try {
|
|
58
|
-
fs.chmodSync(filePath, 0o600);
|
|
59
|
-
} catch {
|
|
60
|
-
// best effort
|
|
61
|
-
}
|
|
62
|
-
return next;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function loadWeixinAccount(config, accountId) {
|
|
66
|
-
const normalized = normalizeAccountId(accountId);
|
|
67
|
-
if (!normalized) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
const raw = fs.readFileSync(resolveAccountPath(config, normalized), "utf8");
|
|
72
|
-
const parsed = JSON.parse(raw);
|
|
73
|
-
if (!parsed || typeof parsed !== "object") {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
56
|
+
fs.writeFileSync(filePath, JSON.stringify(next, null, 2), "utf8");
|
|
57
|
+
try {
|
|
58
|
+
fs.chmodSync(filePath, 0o600);
|
|
59
|
+
} catch {
|
|
60
|
+
// best effort
|
|
61
|
+
}
|
|
62
|
+
return next;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function loadWeixinAccount(config, accountId) {
|
|
66
|
+
const normalized = normalizeAccountId(accountId);
|
|
67
|
+
if (!normalized) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const raw = fs.readFileSync(resolveAccountPath(config, normalized), "utf8");
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
if (!parsed || typeof parsed !== "object") {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
76
|
return {
|
|
77
77
|
accountId: normalized,
|
|
78
78
|
rawAccountId: typeof parsed.rawAccountId === "string" ? parsed.rawAccountId : "",
|
|
@@ -85,51 +85,51 @@ function loadWeixinAccount(config, accountId) {
|
|
|
85
85
|
savedAt: typeof parsed.savedAt === "string" ? parsed.savedAt : "",
|
|
86
86
|
};
|
|
87
87
|
} catch {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function listWeixinAccounts(config) {
|
|
93
|
-
ensureAccountsDir(config);
|
|
94
|
-
const files = fs.readdirSync(config.accountsDir, { withFileTypes: true });
|
|
95
|
-
return files
|
|
96
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith(".json") && !entry.name.endsWith(".context-tokens.json"))
|
|
97
|
-
.map((entry) => loadWeixinAccount(config, entry.name.slice(0, -5)))
|
|
98
|
-
.filter(Boolean)
|
|
99
|
-
.sort((left, right) => String(right.savedAt || "").localeCompare(String(left.savedAt || "")));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function resolveSelectedAccount(config) {
|
|
103
|
-
if (config.accountId) {
|
|
104
|
-
const account = loadWeixinAccount(config, config.accountId);
|
|
105
|
-
if (!account) {
|
|
106
|
-
throw new Error(`未找到微信账号: ${config.accountId}`);
|
|
107
|
-
}
|
|
108
|
-
if (!account.token) {
|
|
109
|
-
throw new Error(`微信账号缺少 token: ${account.accountId},请重新执行 login`);
|
|
110
|
-
}
|
|
111
|
-
return account;
|
|
112
|
-
}
|
|
113
|
-
const accounts = listWeixinAccounts(config);
|
|
114
|
-
if (!accounts.length) {
|
|
115
|
-
throw new Error("当前没有已保存的微信账号,请先执行 `npm run login`");
|
|
116
|
-
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function listWeixinAccounts(config) {
|
|
93
|
+
ensureAccountsDir(config);
|
|
94
|
+
const files = fs.readdirSync(config.accountsDir, { withFileTypes: true });
|
|
95
|
+
return files
|
|
96
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json") && !entry.name.endsWith(".context-tokens.json"))
|
|
97
|
+
.map((entry) => loadWeixinAccount(config, entry.name.slice(0, -5)))
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.sort((left, right) => String(right.savedAt || "").localeCompare(String(left.savedAt || "")));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function resolveSelectedAccount(config) {
|
|
103
|
+
if (config.accountId) {
|
|
104
|
+
const account = loadWeixinAccount(config, config.accountId);
|
|
105
|
+
if (!account) {
|
|
106
|
+
throw new Error(`未找到微信账号: ${config.accountId}`);
|
|
107
|
+
}
|
|
108
|
+
if (!account.token) {
|
|
109
|
+
throw new Error(`微信账号缺少 token: ${account.accountId},请重新执行 login`);
|
|
110
|
+
}
|
|
111
|
+
return account;
|
|
112
|
+
}
|
|
113
|
+
const accounts = listWeixinAccounts(config);
|
|
114
|
+
if (!accounts.length) {
|
|
115
|
+
throw new Error("当前没有已保存的微信账号,请先执行 `npm run login`");
|
|
116
|
+
}
|
|
117
117
|
if (accounts.length > 1) {
|
|
118
118
|
const accountIds = accounts.map((account) => account.accountId).join(", ");
|
|
119
119
|
throw new Error(`检测到多个微信账号,请设置 CODEKSEI_ACCOUNT_ID(或旧的 CYBERBOSS_ACCOUNT_ID)。可选值: ${accountIds}`);
|
|
120
120
|
}
|
|
121
|
-
if (!accounts[0].token) {
|
|
122
|
-
throw new Error(`微信账号缺少 token: ${accounts[0].accountId},请重新执行 login`);
|
|
123
|
-
}
|
|
124
|
-
return accounts[0];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
module.exports = {
|
|
128
|
-
deleteWeixinAccount,
|
|
129
|
-
listWeixinAccounts,
|
|
130
|
-
loadWeixinAccount,
|
|
131
|
-
normalizeAccountId,
|
|
132
|
-
resolveAccountPath,
|
|
133
|
-
resolveSelectedAccount,
|
|
134
|
-
saveWeixinAccount,
|
|
135
|
-
};
|
|
121
|
+
if (!accounts[0].token) {
|
|
122
|
+
throw new Error(`微信账号缺少 token: ${accounts[0].accountId},请重新执行 login`);
|
|
123
|
+
}
|
|
124
|
+
return accounts[0];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
deleteWeixinAccount,
|
|
129
|
+
listWeixinAccounts,
|
|
130
|
+
loadWeixinAccount,
|
|
131
|
+
normalizeAccountId,
|
|
132
|
+
resolveAccountPath,
|
|
133
|
+
resolveSelectedAccount,
|
|
134
|
+
saveWeixinAccount,
|
|
135
|
+
};
|
|
@@ -7,15 +7,15 @@ const DEFAULT_API_TIMEOUT_MS = 15_000;
|
|
|
7
7
|
const DEFAULT_CONFIG_TIMEOUT_MS = 10_000;
|
|
8
8
|
const MAX_RESPONSE_BODY_BYTES = 64 << 20;
|
|
9
9
|
const CHANNEL_VERSION = PRIMARY_CHANNEL_VERSION;
|
|
10
|
-
|
|
11
|
-
function buildBaseInfo() {
|
|
12
|
-
return { channel_version: CHANNEL_VERSION };
|
|
13
|
-
}
|
|
14
|
-
|
|
10
|
+
|
|
11
|
+
function buildBaseInfo() {
|
|
12
|
+
return { channel_version: CHANNEL_VERSION };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
15
|
function ensureTrailingSlash(url) {
|
|
16
16
|
return url.endsWith("/") ? url : `${url}/`;
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
async function apiPost({
|
|
20
20
|
baseUrl,
|
|
21
21
|
endpoint,
|
|
@@ -38,19 +38,19 @@ async function apiPost({
|
|
|
38
38
|
body,
|
|
39
39
|
signal: controller.signal,
|
|
40
40
|
});
|
|
41
|
-
const raw = await response.text();
|
|
42
|
-
if (Buffer.byteLength(raw, "utf8") > MAX_RESPONSE_BODY_BYTES) {
|
|
43
|
-
throw new Error(`${label} response body exceeds ${MAX_RESPONSE_BODY_BYTES} bytes`);
|
|
44
|
-
}
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
throw new Error(`${label} http ${response.status}: ${truncateForLog(raw, 512)}`);
|
|
47
|
-
}
|
|
48
|
-
return raw;
|
|
49
|
-
} finally {
|
|
50
|
-
clearTimeout(timer);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
41
|
+
const raw = await response.text();
|
|
42
|
+
if (Buffer.byteLength(raw, "utf8") > MAX_RESPONSE_BODY_BYTES) {
|
|
43
|
+
throw new Error(`${label} response body exceeds ${MAX_RESPONSE_BODY_BYTES} bytes`);
|
|
44
|
+
}
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new Error(`${label} http ${response.status}: ${truncateForLog(raw, 512)}`);
|
|
47
|
+
}
|
|
48
|
+
return raw;
|
|
49
|
+
} finally {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
54
|
function parseJson(raw, label) {
|
|
55
55
|
try {
|
|
56
56
|
return JSON.parse(raw);
|
|
@@ -67,12 +67,12 @@ function assertApiSuccess(parsed, label) {
|
|
|
67
67
|
}
|
|
68
68
|
return parsed;
|
|
69
69
|
}
|
|
70
|
-
|
|
71
|
-
function truncateForLog(value, max) {
|
|
72
|
-
const text = typeof value === "string" ? value : String(value || "");
|
|
73
|
-
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
74
|
-
}
|
|
75
|
-
|
|
70
|
+
|
|
71
|
+
function truncateForLog(value, max) {
|
|
72
|
+
const text = typeof value === "string" ? value : String(value || "");
|
|
73
|
+
return text.length <= max ? text : `${text.slice(0, max)}…`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
76
|
async function getUpdatesV2({
|
|
77
77
|
baseUrl,
|
|
78
78
|
token,
|
|
@@ -84,11 +84,11 @@ async function getUpdatesV2({
|
|
|
84
84
|
const payload = JSON.stringify({
|
|
85
85
|
get_updates_buf: getUpdatesBuf,
|
|
86
86
|
base_info: buildBaseInfo(),
|
|
87
|
-
});
|
|
88
|
-
try {
|
|
89
|
-
const raw = await apiPost({
|
|
90
|
-
baseUrl,
|
|
91
|
-
endpoint: "ilink/bot/getupdates",
|
|
87
|
+
});
|
|
88
|
+
try {
|
|
89
|
+
const raw = await apiPost({
|
|
90
|
+
baseUrl,
|
|
91
|
+
endpoint: "ilink/bot/getupdates",
|
|
92
92
|
token,
|
|
93
93
|
body: payload,
|
|
94
94
|
timeoutMs,
|
|
@@ -98,16 +98,16 @@ async function getUpdatesV2({
|
|
|
98
98
|
});
|
|
99
99
|
return parseJson(raw, "getUpdates");
|
|
100
100
|
} catch (error) {
|
|
101
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
102
|
-
return { ret: 0, msgs: [], get_updates_buf: getUpdatesBuf };
|
|
103
|
-
}
|
|
104
|
-
if (String(error?.message || "").includes("aborted")) {
|
|
105
|
-
return { ret: 0, msgs: [], get_updates_buf: getUpdatesBuf };
|
|
106
|
-
}
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
101
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
102
|
+
return { ret: 0, msgs: [], get_updates_buf: getUpdatesBuf };
|
|
103
|
+
}
|
|
104
|
+
if (String(error?.message || "").includes("aborted")) {
|
|
105
|
+
return { ret: 0, msgs: [], get_updates_buf: getUpdatesBuf };
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
111
|
async function sendTextV2({
|
|
112
112
|
baseUrl,
|
|
113
113
|
token,
|
|
@@ -121,16 +121,16 @@ async function sendTextV2({
|
|
|
121
121
|
if (!String(contextToken || "").trim()) {
|
|
122
122
|
throw new Error("weixin-v2 sendText requires contextToken");
|
|
123
123
|
}
|
|
124
|
-
const itemList = [];
|
|
125
|
-
if (String(text || "").trim()) {
|
|
126
|
-
itemList.push({
|
|
127
|
-
type: 1,
|
|
128
|
-
text_item: { text: String(text) },
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
if (!itemList.length) {
|
|
132
|
-
throw new Error("weixin-v2 sendText requires non-empty text");
|
|
133
|
-
}
|
|
124
|
+
const itemList = [];
|
|
125
|
+
if (String(text || "").trim()) {
|
|
126
|
+
itemList.push({
|
|
127
|
+
type: 1,
|
|
128
|
+
text_item: { text: String(text) },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (!itemList.length) {
|
|
132
|
+
throw new Error("weixin-v2 sendText requires non-empty text");
|
|
133
|
+
}
|
|
134
134
|
return sendMessageV2({
|
|
135
135
|
baseUrl,
|
|
136
136
|
token,
|