codex-to-im 1.0.42 → 1.0.44
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/cli.mjs +37 -9
- package/dist/daemon.mjs +3091 -9102
- package/dist/ui-server.mjs +113 -63
- package/package.json +2 -4
- package/references/setup-guides.md +34 -156
- package/references/token-validation.md +28 -44
- package/references/troubleshooting.md +10 -11
- package/scripts/build.js +2 -7
- package/scripts/daemon.sh +11 -30
- package/scripts/doctor.sh +35 -280
- package/scripts/supervisor-macos.sh +9 -28
|
@@ -1,44 +1,28 @@
|
|
|
1
|
-
# Token Validation Commands
|
|
2
|
-
|
|
3
|
-
After writing config.env
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
curl -s "
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Step 1 — Get access token:
|
|
30
|
-
```bash
|
|
31
|
-
curl -s -X POST "https://bots.qq.com/app/getAppAccessToken" \
|
|
32
|
-
-H "Content-Type: application/json" \
|
|
33
|
-
-d '{"appId":"...","clientSecret":"..."}'
|
|
34
|
-
```
|
|
35
|
-
Expected: response contains `access_token`.
|
|
36
|
-
|
|
37
|
-
Step 2 — Verify gateway connectivity:
|
|
38
|
-
```bash
|
|
39
|
-
curl -s "https://api.sgroup.qq.com/gateway" \
|
|
40
|
-
-H "Authorization: QQBot <access_token>"
|
|
41
|
-
```
|
|
42
|
-
Expected: response contains a gateway URL.
|
|
43
|
-
|
|
44
|
-
If either step fails, verify the App ID and App Secret from https://q.qq.com.
|
|
1
|
+
# Token Validation Commands
|
|
2
|
+
|
|
3
|
+
After writing `config.env`, validate each enabled platform's credentials to catch typos and configuration errors early.
|
|
4
|
+
|
|
5
|
+
## Feishu / Lark
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
curl -s -X POST "${DOMAIN}/open-apis/auth/v3/tenant_access_token/internal" \
|
|
9
|
+
-H "Content-Type: application/json" \
|
|
10
|
+
-d '{"app_id":"...","app_secret":"..."}'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Expected: response contains `"code":0`. If not, check that App ID, App Secret, and site/domain match the Feishu Developer Console.
|
|
14
|
+
|
|
15
|
+
## Weixin
|
|
16
|
+
|
|
17
|
+
Weixin uses the local linked-account store instead of a bot token. Validate it with:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
codex-to-im status
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
If no linked account is available, run:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd /path/to/codex-to-im
|
|
27
|
+
npm run weixin:login
|
|
28
|
+
```
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
1. Run the local doctor script or inspect the workbench logs to identify the issue
|
|
10
10
|
2. Check that Node.js >= 20 is installed: `node --version`
|
|
11
|
-
3. Check that
|
|
11
|
+
3. Check that Codex CLI is available: `codex --version`
|
|
12
12
|
4. Verify config exists: `ls -la ~/.codex-to-im/config.env`
|
|
13
13
|
5. Check logs for startup errors in `~/.codex-to-im/logs/`
|
|
14
14
|
|
|
@@ -23,12 +23,11 @@
|
|
|
23
23
|
|
|
24
24
|
**Steps**:
|
|
25
25
|
|
|
26
|
-
1. Verify the
|
|
27
|
-
2. Check allowed user IDs in config -- if set, only listed users can interact
|
|
28
|
-
3. For
|
|
29
|
-
4. For
|
|
30
|
-
5.
|
|
31
|
-
6. Check recent logs for incoming message events under `~/.codex-to-im/logs/`
|
|
26
|
+
1. Verify the channel credentials with the local workbench test tools or doctor script
|
|
27
|
+
2. Check allowed user IDs in config -- if set, only listed users can interact
|
|
28
|
+
3. For Feishu: confirm the app has been approved and event subscriptions are configured
|
|
29
|
+
4. For Weixin: confirm the linked account is logged in and assigned to the channel instance
|
|
30
|
+
5. Check recent logs for incoming message events under `~/.codex-to-im/logs/`
|
|
32
31
|
|
|
33
32
|
## Feishu streaming cards not working
|
|
34
33
|
|
|
@@ -52,12 +51,12 @@
|
|
|
52
51
|
|
|
53
52
|
## Permission timeout
|
|
54
53
|
|
|
55
|
-
**Symptoms**:
|
|
54
|
+
**Symptoms**: Codex session starts but times out waiting for tool approval.
|
|
56
55
|
|
|
57
56
|
**Steps**:
|
|
58
57
|
|
|
59
|
-
1. The bridge runs
|
|
60
|
-
2.
|
|
58
|
+
1. The bridge runs Codex through the SDK/CLI; ensure the configured sandbox and approval policy match the requested task
|
|
59
|
+
2. Use the IM permission prompt to approve the requested tool, or adjust Codex approval settings if you expect unattended execution
|
|
61
60
|
3. Check network connectivity if the timeout occurs during API calls
|
|
62
61
|
|
|
63
62
|
## High memory usage
|
|
@@ -71,7 +70,7 @@
|
|
|
71
70
|
```
|
|
72
71
|
Stop the bridge, then start it again from the local workbench
|
|
73
72
|
```
|
|
74
|
-
3. If the issue persists, check how many concurrent sessions are active -- each
|
|
73
|
+
3. If the issue persists, check how many concurrent sessions are active -- each Codex session consumes memory
|
|
75
74
|
4. Review logs for error loops that may cause memory leaks
|
|
76
75
|
|
|
77
76
|
## Stale PID file
|
package/scripts/build.js
CHANGED
|
@@ -6,17 +6,12 @@ const common = {
|
|
|
6
6
|
format: 'esm',
|
|
7
7
|
target: 'node20',
|
|
8
8
|
external: [
|
|
9
|
-
// SDK must stay external — it spawns a CLI subprocess and resolves
|
|
10
|
-
// dist/cli.js relative to its own package location. Bundling it
|
|
11
|
-
// breaks that path resolution.
|
|
12
|
-
'@anthropic-ai/claude-agent-sdk',
|
|
13
9
|
'@openai/codex-sdk',
|
|
14
10
|
// Keep large IM SDKs external so global/local npm installs resolve them
|
|
15
11
|
// from node_modules instead of inflating daemon.mjs.
|
|
16
12
|
'@larksuiteoapi/node-sdk',
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'bufferutil', 'utf-8-validate', 'zlib-sync', 'erlpack',
|
|
13
|
+
// ws optional native deps
|
|
14
|
+
'bufferutil', 'utf-8-validate',
|
|
20
15
|
// Node.js built-ins
|
|
21
16
|
'fs', 'path', 'os', 'crypto', 'http', 'https', 'net', 'tls',
|
|
22
17
|
'stream', 'events', 'url', 'util', 'child_process', 'worker_threads',
|
package/scripts/daemon.sh
CHANGED
|
@@ -38,34 +38,16 @@ ensure_built() {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
# Clean environment for subprocess isolation.
|
|
41
|
-
clean_env() {
|
|
42
|
-
unset CLAUDECODE 2>/dev/null || true
|
|
43
|
-
|
|
44
|
-
local
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
codex)
|
|
52
|
-
while IFS='=' read -r name _; do
|
|
53
|
-
case "$name" in ANTHROPIC_*) unset "$name" 2>/dev/null || true ;; esac
|
|
54
|
-
done < <(env)
|
|
55
|
-
;;
|
|
56
|
-
claude)
|
|
57
|
-
# Keep ANTHROPIC_* (from config.env) — needed for third-party API providers.
|
|
58
|
-
# Strip OPENAI_* to avoid cross-runtime leakage.
|
|
59
|
-
while IFS='=' read -r name _; do
|
|
60
|
-
case "$name" in OPENAI_*) unset "$name" 2>/dev/null || true ;; esac
|
|
61
|
-
done < <(env)
|
|
62
|
-
;;
|
|
63
|
-
auto)
|
|
64
|
-
# Keep both ANTHROPIC_* and OPENAI_* for auto mode
|
|
65
|
-
;;
|
|
66
|
-
esac
|
|
67
|
-
fi
|
|
68
|
-
}
|
|
41
|
+
clean_env() {
|
|
42
|
+
unset CLAUDECODE 2>/dev/null || true
|
|
43
|
+
|
|
44
|
+
local mode="${CTI_ENV_ISOLATION:-inherit}"
|
|
45
|
+
if [ "$mode" = "strict" ]; then
|
|
46
|
+
while IFS='=' read -r name _; do
|
|
47
|
+
case "$name" in ANTHROPIC_*) unset "$name" 2>/dev/null || true ;; esac
|
|
48
|
+
done < <(env)
|
|
49
|
+
fi
|
|
50
|
+
}
|
|
69
51
|
|
|
70
52
|
read_pid() {
|
|
71
53
|
[ -f "$PID_FILE" ] && cat "$PID_FILE" 2>/dev/null || echo ""
|
|
@@ -133,8 +115,7 @@ case "${1:-help}" in
|
|
|
133
115
|
exit 1
|
|
134
116
|
fi
|
|
135
117
|
|
|
136
|
-
# Source config.env BEFORE clean_env so
|
|
137
|
-
# and other CTI_* flags are available when clean_env checks them.
|
|
118
|
+
# Source config.env BEFORE clean_env so CTI_* flags are available.
|
|
138
119
|
[ -f "$CTI_HOME/config.env" ] && set -a && source "$CTI_HOME/config.env" && set +a
|
|
139
120
|
|
|
140
121
|
clean_env
|
package/scripts/doctor.sh
CHANGED
|
@@ -37,210 +37,26 @@ get_config() { grep "^$1=" "$CONFIG_FILE" 2>/dev/null | head -1 | cut -d= -f2- |
|
|
|
37
37
|
|
|
38
38
|
# --- Read runtime setting ---
|
|
39
39
|
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
40
|
-
CTI_RUNTIME=$(get_config CTI_RUNTIME)
|
|
41
|
-
CTI_RUNTIME="
|
|
42
|
-
echo "Runtime: $CTI_RUNTIME"
|
|
43
|
-
echo ""
|
|
44
|
-
|
|
45
|
-
# ---
|
|
46
|
-
if
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
local ver
|
|
61
|
-
ver=$("$cand" --version 2>/dev/null || true)
|
|
62
|
-
[ -z "$ver" ] && return 1
|
|
63
|
-
local major
|
|
64
|
-
major=$(echo "$ver" | sed -E -n 's/^[^0-9]*([0-9]+)\..*/\1/p' | head -1)
|
|
65
|
-
if [ -z "$major" ] || ! [ "$major" -ge 2 ] 2>/dev/null; then
|
|
66
|
-
echo " (skipping $cand — version $ver is too old, need >= 2.x)"
|
|
67
|
-
return 1
|
|
68
|
-
fi
|
|
69
|
-
# Version OK — check flags
|
|
70
|
-
local help_text
|
|
71
|
-
help_text=$("$cand" --help 2>&1 || true)
|
|
72
|
-
for flag in $REQUIRED_FLAGS; do
|
|
73
|
-
if ! echo "$help_text" | grep -q "$flag"; then
|
|
74
|
-
echo " (skipping $cand — version $ver OK but missing --$flag)"
|
|
75
|
-
return 1
|
|
76
|
-
fi
|
|
77
|
-
done
|
|
78
|
-
# Fully compatible
|
|
79
|
-
CLAUDE_PATH="$cand"
|
|
80
|
-
CLAUDE_VER="$ver"
|
|
81
|
-
CLAUDE_COMPAT=0
|
|
82
|
-
return 0
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
# 1. Explicit env var — if set, daemon uses it unconditionally (no fallback).
|
|
86
|
-
# Doctor must mirror this: report on this path only, never scan further.
|
|
87
|
-
CTI_EXE=$(get_config CTI_CLAUDE_CODE_EXECUTABLE 2>/dev/null || true)
|
|
88
|
-
if [ -n "$CTI_EXE" ]; then
|
|
89
|
-
if [ -x "$CTI_EXE" ]; then
|
|
90
|
-
if ! try_candidate "$CTI_EXE"; then
|
|
91
|
-
# Explicit path is set but incompatible — daemon WILL use it and fail.
|
|
92
|
-
# Report it as the selected CLI so the user sees the real problem.
|
|
93
|
-
CLAUDE_PATH="$CTI_EXE"
|
|
94
|
-
CLAUDE_VER=$("$CTI_EXE" --version 2>/dev/null || echo "unknown")
|
|
95
|
-
# CLAUDE_COMPAT stays 1 (incompatible) — checks below will report failure
|
|
96
|
-
fi
|
|
97
|
-
else
|
|
98
|
-
CLAUDE_PATH="$CTI_EXE"
|
|
99
|
-
CLAUDE_VER="(not executable)"
|
|
100
|
-
fi
|
|
101
|
-
fi
|
|
102
|
-
|
|
103
|
-
# 2. All PATH candidates (only if no explicit env var was set)
|
|
104
|
-
if [ -z "$CTI_EXE" ] && [ -z "$CLAUDE_PATH" ]; then
|
|
105
|
-
ALL_CLAUDES=$(which -a claude 2>/dev/null || true)
|
|
106
|
-
for cand in $ALL_CLAUDES; do
|
|
107
|
-
try_candidate "$cand" && break
|
|
108
|
-
done
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
# 3. Well-known locations (only if no explicit env var was set)
|
|
112
|
-
if [ -z "$CTI_EXE" ] && [ -z "$CLAUDE_PATH" ]; then
|
|
113
|
-
for cand in \
|
|
114
|
-
"$HOME/.claude/local/claude" \
|
|
115
|
-
"$HOME/.local/bin/claude" \
|
|
116
|
-
"/usr/local/bin/claude" \
|
|
117
|
-
"/opt/homebrew/bin/claude" \
|
|
118
|
-
"$HOME/.npm-global/bin/claude"; do
|
|
119
|
-
try_candidate "$cand" && break
|
|
120
|
-
done
|
|
121
|
-
fi
|
|
122
|
-
|
|
123
|
-
if [ -n "$CLAUDE_PATH" ] && [ "$CLAUDE_COMPAT" = "0" ]; then
|
|
124
|
-
check "Claude CLI compatible (${CLAUDE_VER} at ${CLAUDE_PATH})" 0
|
|
125
|
-
elif [ -n "$CLAUDE_PATH" ]; then
|
|
126
|
-
# Path found but incompatible (too old, missing flags, or not executable)
|
|
127
|
-
check "Claude CLI compatible (${CLAUDE_VER} at ${CLAUDE_PATH} — incompatible, see above)" 1
|
|
128
|
-
else
|
|
129
|
-
if [ "$CTI_RUNTIME" = "claude" ]; then
|
|
130
|
-
check "Claude CLI available (not found in PATH or common locations)" 1
|
|
131
|
-
else
|
|
132
|
-
check "Claude CLI available (not found — will use Codex fallback)" 0
|
|
133
|
-
fi
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
# --- Claude CLI authenticated ---
|
|
137
|
-
# Skip this check if third-party API credentials are configured in config.env.
|
|
138
|
-
# In that mode the bridge authenticates via ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN,
|
|
139
|
-
# not via `claude auth login`, so a missing interactive login is expected and harmless.
|
|
140
|
-
HAS_THIRD_PARTY_AUTH=false
|
|
141
|
-
if [ -f "$CONFIG_FILE" ] && grep -qE "^ANTHROPIC_(API_KEY|AUTH_TOKEN)=" "$CONFIG_FILE" 2>/dev/null; then
|
|
142
|
-
HAS_THIRD_PARTY_AUTH=true
|
|
143
|
-
fi
|
|
144
|
-
if [ -n "$CLAUDE_PATH" ] && [ "$CLAUDE_COMPAT" = "0" ]; then
|
|
145
|
-
if [ "$HAS_THIRD_PARTY_AUTH" = "true" ]; then
|
|
146
|
-
check "Claude CLI auth (skipped — using third-party API credentials from config.env)" 0
|
|
147
|
-
else
|
|
148
|
-
AUTH_OUT=$("$CLAUDE_PATH" auth status 2>&1 || true)
|
|
149
|
-
if echo "$AUTH_OUT" | grep -qiE 'loggedIn.*true|logged.in'; then
|
|
150
|
-
check "Claude CLI authenticated" 0
|
|
151
|
-
else
|
|
152
|
-
check "Claude CLI authenticated (run 'claude auth login')" 1
|
|
153
|
-
fi
|
|
154
|
-
fi
|
|
155
|
-
fi
|
|
156
|
-
|
|
157
|
-
# --- ANTHROPIC_* env reachability ---
|
|
158
|
-
# Check whether ANTHROPIC_* vars are configured in config.env.
|
|
159
|
-
# This is what matters for the daemon — the current shell env is irrelevant
|
|
160
|
-
# because on macOS the daemon runs under launchd with only plist env vars.
|
|
161
|
-
HAS_ANTHROPIC_CONFIG=false
|
|
162
|
-
if [ -f "$CONFIG_FILE" ]; then
|
|
163
|
-
if grep -q "^ANTHROPIC_" "$CONFIG_FILE" 2>/dev/null; then
|
|
164
|
-
HAS_ANTHROPIC_CONFIG=true
|
|
165
|
-
fi
|
|
166
|
-
fi
|
|
167
|
-
if [ "$HAS_ANTHROPIC_CONFIG" = "true" ]; then
|
|
168
|
-
check "ANTHROPIC_* vars in config.env (third-party API provider)" 0
|
|
169
|
-
|
|
170
|
-
PLIST_FILE="$HOME/Library/LaunchAgents/com.codex-to-im.bridge.plist"
|
|
171
|
-
|
|
172
|
-
# On macOS, verify the launchd plist also has the vars
|
|
173
|
-
if [ "$(uname -s)" = "Darwin" ] && [ -f "$PLIST_FILE" ]; then
|
|
174
|
-
if grep -q "ANTHROPIC_" "$PLIST_FILE" 2>/dev/null; then
|
|
175
|
-
check "ANTHROPIC_* vars in launchd plist" 0
|
|
176
|
-
else
|
|
177
|
-
check "ANTHROPIC_* vars in launchd plist (NOT present — restart bridge to regenerate plist)" 1
|
|
178
|
-
fi
|
|
179
|
-
fi
|
|
180
|
-
|
|
181
|
-
# If bridge is running, verify the LIVE process has the vars.
|
|
182
|
-
# The plist may be correct on disk but if the daemon hasn't been
|
|
183
|
-
# restarted since the plist was regenerated, it still runs with the
|
|
184
|
-
# old environment.
|
|
185
|
-
BRIDGE_PID=$(cat "$PID_FILE" 2>/dev/null || true)
|
|
186
|
-
if [ -n "$BRIDGE_PID" ] && kill -0 "$BRIDGE_PID" 2>/dev/null; then
|
|
187
|
-
# ps eww shows the process environment on macOS/Linux
|
|
188
|
-
PROC_ENV=$(ps eww -p "$BRIDGE_PID" 2>/dev/null || true)
|
|
189
|
-
if echo "$PROC_ENV" | grep -q "ANTHROPIC_"; then
|
|
190
|
-
check "Running bridge process has ANTHROPIC_* env vars" 0
|
|
191
|
-
else
|
|
192
|
-
check "Running bridge process has ANTHROPIC_* env vars (NOT in process env — restart the bridge)" 1
|
|
193
|
-
fi
|
|
194
|
-
fi
|
|
195
|
-
else
|
|
196
|
-
check "ANTHROPIC_* vars in config.env (not set — OK if using default Anthropic auth)" 0
|
|
197
|
-
fi
|
|
198
|
-
|
|
199
|
-
# --- SDK cli.js resolvable ---
|
|
200
|
-
SDK_CLI=""
|
|
201
|
-
for candidate in \
|
|
202
|
-
"$SKILL_DIR/node_modules/@anthropic-ai/claude-agent-sdk/cli.js" \
|
|
203
|
-
"$SKILL_DIR/node_modules/@anthropic-ai/claude-agent-sdk/dist/cli.js"; do
|
|
204
|
-
if [ -f "$candidate" ]; then
|
|
205
|
-
SDK_CLI="$candidate"
|
|
206
|
-
break
|
|
207
|
-
fi
|
|
208
|
-
done
|
|
209
|
-
if [ -n "$SDK_CLI" ]; then
|
|
210
|
-
check "Claude SDK cli.js exists ($SDK_CLI)" 0
|
|
211
|
-
else
|
|
212
|
-
if [ "$CTI_RUNTIME" = "claude" ]; then
|
|
213
|
-
check "Claude SDK cli.js exists (not found — run 'npm install' in $SKILL_DIR)" 1
|
|
214
|
-
else
|
|
215
|
-
check "Claude SDK cli.js exists (not found — OK for auto/codex mode)" 0
|
|
216
|
-
fi
|
|
217
|
-
fi
|
|
218
|
-
fi
|
|
219
|
-
|
|
220
|
-
# --- Codex checks (codex/auto modes) ---
|
|
221
|
-
if [ "$CTI_RUNTIME" = "codex" ] || [ "$CTI_RUNTIME" = "auto" ]; then
|
|
222
|
-
if command -v codex &>/dev/null; then
|
|
223
|
-
CODEX_VER=$(codex --version 2>/dev/null || echo "unknown")
|
|
224
|
-
check "Codex CLI available (${CODEX_VER})" 0
|
|
225
|
-
else
|
|
226
|
-
if [ "$CTI_RUNTIME" = "codex" ]; then
|
|
227
|
-
check "Codex CLI available (not found in PATH)" 1
|
|
228
|
-
else
|
|
229
|
-
check "Codex CLI available (not found — will use Claude)" 0
|
|
230
|
-
fi
|
|
231
|
-
fi
|
|
232
|
-
|
|
233
|
-
# Check @openai/codex-sdk
|
|
234
|
-
CODEX_SDK="$SKILL_DIR/node_modules/@openai/codex-sdk"
|
|
235
|
-
if [ -d "$CODEX_SDK" ]; then
|
|
236
|
-
check "@openai/codex-sdk installed" 0
|
|
237
|
-
else
|
|
238
|
-
if [ "$CTI_RUNTIME" = "codex" ]; then
|
|
239
|
-
check "@openai/codex-sdk installed (not found — run 'npm install' in $SKILL_DIR)" 1
|
|
240
|
-
else
|
|
241
|
-
check "@openai/codex-sdk installed (not found — OK for auto/claude mode)" 0
|
|
242
|
-
fi
|
|
243
|
-
fi
|
|
40
|
+
CTI_RUNTIME=$(get_config CTI_RUNTIME)
|
|
41
|
+
CTI_RUNTIME="codex"
|
|
42
|
+
echo "Runtime: $CTI_RUNTIME"
|
|
43
|
+
echo ""
|
|
44
|
+
|
|
45
|
+
# --- Codex checks ---
|
|
46
|
+
if command -v codex &>/dev/null; then
|
|
47
|
+
CODEX_VER=$(codex --version 2>/dev/null || echo "unknown")
|
|
48
|
+
check "Codex CLI available (${CODEX_VER})" 0
|
|
49
|
+
else
|
|
50
|
+
check "Codex CLI available (not found in PATH)" 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
# Check @openai/codex-sdk
|
|
54
|
+
CODEX_SDK="$SKILL_DIR/node_modules/@openai/codex-sdk"
|
|
55
|
+
if [ -d "$CODEX_SDK" ]; then
|
|
56
|
+
check "@openai/codex-sdk installed" 0
|
|
57
|
+
else
|
|
58
|
+
check "@openai/codex-sdk installed (not found — run 'npm install' in $SKILL_DIR)" 1
|
|
59
|
+
fi
|
|
244
60
|
|
|
245
61
|
# Check Codex auth: any of CTI_CODEX_API_KEY / CODEX_API_KEY / OPENAI_API_KEY,
|
|
246
62
|
# or `codex auth status` showing logged-in (interactive login).
|
|
@@ -253,18 +69,13 @@ if [ "$CTI_RUNTIME" = "codex" ] || [ "$CTI_RUNTIME" = "auto" ]; then
|
|
|
253
69
|
CODEX_AUTH=0
|
|
254
70
|
fi
|
|
255
71
|
fi
|
|
256
|
-
if [ "$CODEX_AUTH" = "0" ]; then
|
|
257
|
-
check "Codex auth available (API key or login)" 0
|
|
258
|
-
else
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
fi
|
|
264
|
-
fi
|
|
265
|
-
fi
|
|
266
|
-
|
|
267
|
-
# --- dist/daemon.mjs freshness ---
|
|
72
|
+
if [ "$CODEX_AUTH" = "0" ]; then
|
|
73
|
+
check "Codex auth available (API key or login)" 0
|
|
74
|
+
else
|
|
75
|
+
check "Codex auth available (set OPENAI_API_KEY or run 'codex auth login')" 1
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# --- dist/daemon.mjs freshness ---
|
|
268
79
|
DAEMON_MJS="$SKILL_DIR/dist/daemon.mjs"
|
|
269
80
|
if [ -f "$DAEMON_MJS" ]; then
|
|
270
81
|
STALE_SRC=$(find "$SKILL_DIR/src" -name '*.ts' -newer "$DAEMON_MJS" 2>/dev/null | head -1)
|
|
@@ -295,25 +106,10 @@ if [ -f "$CONFIG_FILE" ]; then
|
|
|
295
106
|
fi
|
|
296
107
|
|
|
297
108
|
# --- Load config for channel checks ---
|
|
298
|
-
if [ -f "$CONFIG_FILE" ]; then
|
|
299
|
-
CTI_CHANNELS=$(get_config CTI_ENABLED_CHANNELS)
|
|
300
|
-
|
|
301
|
-
# ---
|
|
302
|
-
if echo "$CTI_CHANNELS" | grep -q telegram; then
|
|
303
|
-
TG_TOKEN=$(get_config CTI_TG_BOT_TOKEN)
|
|
304
|
-
if [ -n "$TG_TOKEN" ]; then
|
|
305
|
-
TG_RESULT=$(curl -s --max-time 5 "https://api.telegram.org/bot${TG_TOKEN}/getMe" 2>/dev/null || echo '{"ok":false}')
|
|
306
|
-
if echo "$TG_RESULT" | grep -q '"ok":true'; then
|
|
307
|
-
check "Telegram bot token is valid" 0
|
|
308
|
-
else
|
|
309
|
-
check "Telegram bot token is valid (getMe failed)" 1
|
|
310
|
-
fi
|
|
311
|
-
else
|
|
312
|
-
check "Telegram bot token configured" 1
|
|
313
|
-
fi
|
|
314
|
-
fi
|
|
315
|
-
|
|
316
|
-
# --- Feishu ---
|
|
109
|
+
if [ -f "$CONFIG_FILE" ]; then
|
|
110
|
+
CTI_CHANNELS=$(get_config CTI_ENABLED_CHANNELS)
|
|
111
|
+
|
|
112
|
+
# --- Feishu ---
|
|
317
113
|
if echo "$CTI_CHANNELS" | grep -q feishu; then
|
|
318
114
|
FS_APP_ID=$(get_config CTI_FEISHU_APP_ID)
|
|
319
115
|
FS_SECRET=$(get_config CTI_FEISHU_APP_SECRET)
|
|
@@ -337,51 +133,10 @@ if [ -f "$CONFIG_FILE" ]; then
|
|
|
337
133
|
fi
|
|
338
134
|
else
|
|
339
135
|
check "Feishu app credentials configured" 1
|
|
340
|
-
fi
|
|
341
|
-
fi
|
|
342
|
-
|
|
343
|
-
# ---
|
|
344
|
-
if echo "$CTI_CHANNELS" | grep -q qq; then
|
|
345
|
-
QQ_APP_ID=$(get_config CTI_QQ_APP_ID)
|
|
346
|
-
QQ_APP_SECRET=$(get_config CTI_QQ_APP_SECRET)
|
|
347
|
-
if [ -n "$QQ_APP_ID" ] && [ -n "$QQ_APP_SECRET" ]; then
|
|
348
|
-
QQ_TOKEN_RESULT=$(curl -s --max-time 10 -X POST "https://bots.qq.com/app/getAppAccessToken" \
|
|
349
|
-
-H "Content-Type: application/json" \
|
|
350
|
-
-d "{\"appId\":\"${QQ_APP_ID}\",\"clientSecret\":\"${QQ_APP_SECRET}\"}" 2>/dev/null || echo '{}')
|
|
351
|
-
QQ_ACCESS_TOKEN=$(echo "$QQ_TOKEN_RESULT" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
|
352
|
-
if [ -n "$QQ_ACCESS_TOKEN" ]; then
|
|
353
|
-
check "QQ app credentials are valid (access_token obtained)" 0
|
|
354
|
-
# Verify gateway availability
|
|
355
|
-
QQ_GW_RESULT=$(curl -s --max-time 10 "https://api.sgroup.qq.com/gateway" \
|
|
356
|
-
-H "Authorization: QQBot ${QQ_ACCESS_TOKEN}" 2>/dev/null || echo '{}')
|
|
357
|
-
if echo "$QQ_GW_RESULT" | grep -q '"url"'; then
|
|
358
|
-
check "QQ gateway is reachable" 0
|
|
359
|
-
else
|
|
360
|
-
check "QQ gateway is reachable (GET /gateway failed)" 1
|
|
361
|
-
fi
|
|
362
|
-
else
|
|
363
|
-
check "QQ app credentials are valid (getAppAccessToken failed)" 1
|
|
364
|
-
fi
|
|
365
|
-
else
|
|
366
|
-
check "QQ app credentials configured" 1
|
|
367
|
-
fi
|
|
368
|
-
fi
|
|
369
|
-
|
|
370
|
-
# --- Discord ---
|
|
371
|
-
if echo "$CTI_CHANNELS" | grep -q discord; then
|
|
372
|
-
DC_TOKEN=$(get_config CTI_DISCORD_BOT_TOKEN)
|
|
373
|
-
if [ -n "$DC_TOKEN" ]; then
|
|
374
|
-
if echo "${DC_TOKEN}" | grep -qE '^[A-Za-z0-9_-]{20,}\.'; then
|
|
375
|
-
check "Discord bot token format" 0
|
|
376
|
-
else
|
|
377
|
-
check "Discord bot token format (does not match expected pattern)" 1
|
|
378
|
-
fi
|
|
379
|
-
else
|
|
380
|
-
check "Discord bot token configured" 1
|
|
381
|
-
fi
|
|
382
|
-
fi
|
|
383
|
-
|
|
384
|
-
# --- Weixin ---
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# --- Weixin ---
|
|
385
140
|
if echo "$CTI_CHANNELS" | grep -q weixin; then
|
|
386
141
|
WX_ACCOUNTS_FILE="$CTI_HOME/data/weixin-accounts.json"
|
|
387
142
|
if [ -f "$WX_ACCOUNTS_FILE" ]; then
|
|
@@ -28,34 +28,15 @@ build_env_dict() {
|
|
|
28
28
|
;; esac
|
|
29
29
|
done < <(env)
|
|
30
30
|
|
|
31
|
-
# Forward
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
[ -z "$val" ] && continue
|
|
41
|
-
dict+="${indent}<key>${var}</key>\n${indent}<string>${val}</string>\n"
|
|
42
|
-
done
|
|
43
|
-
;;
|
|
44
|
-
esac
|
|
45
|
-
case "$runtime" in
|
|
46
|
-
claude|auto)
|
|
47
|
-
# Auto-forward all ANTHROPIC_* env vars (sourced from config.env by daemon.sh).
|
|
48
|
-
# Third-party API providers need these to reach the CLI subprocess.
|
|
49
|
-
while IFS='=' read -r name val; do
|
|
50
|
-
case "$name" in ANTHROPIC_*)
|
|
51
|
-
dict+="${indent}<key>${name}</key>\n${indent}<string>${val}</string>\n"
|
|
52
|
-
;; esac
|
|
53
|
-
done < <(env)
|
|
54
|
-
;;
|
|
55
|
-
esac
|
|
56
|
-
|
|
57
|
-
echo -e "$dict"
|
|
58
|
-
}
|
|
31
|
+
# Forward Codex/OpenAI credentials used by the Codex runtime.
|
|
32
|
+
for var in OPENAI_API_KEY CODEX_API_KEY CTI_CODEX_API_KEY CTI_CODEX_BASE_URL; do
|
|
33
|
+
local val="${!var:-}"
|
|
34
|
+
[ -z "$val" ] && continue
|
|
35
|
+
dict+="${indent}<key>${var}</key>\n${indent}<string>${val}</string>\n"
|
|
36
|
+
done
|
|
37
|
+
|
|
38
|
+
echo -e "$dict"
|
|
39
|
+
}
|
|
59
40
|
|
|
60
41
|
generate_plist() {
|
|
61
42
|
local node_path
|