codex-to-im 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,44 @@
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
+ ## Telegram
6
+
7
+ ```bash
8
+ curl -s "https://api.telegram.org/bot${TOKEN}/getMe"
9
+ ```
10
+ Expected: response contains `"ok":true`. If not, the Bot Token is invalid — re-check with @BotFather.
11
+
12
+ ## Discord
13
+
14
+ Verify token format matches: `[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+`
15
+
16
+ A format mismatch means the token was copied incorrectly from the Discord Developer Portal.
17
+
18
+ ## Feishu / Lark
19
+
20
+ ```bash
21
+ curl -s -X POST "${DOMAIN}/open-apis/auth/v3/tenant_access_token/internal" \
22
+ -H "Content-Type: application/json" \
23
+ -d '{"app_id":"...","app_secret":"..."}'
24
+ ```
25
+ Expected: response contains `"code":0`. If not, check that App ID and App Secret match the Feishu Developer Console.
26
+
27
+ ## QQ
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.
@@ -0,0 +1,88 @@
1
+ # Troubleshooting
2
+
3
+ ## Bridge won't start
4
+
5
+ **Symptoms**: Starting the bridge from the local workbench fails, or the daemon exits immediately.
6
+
7
+ **Steps**:
8
+
9
+ 1. Run the local doctor script or inspect the workbench logs to identify the issue
10
+ 2. Check that Node.js >= 20 is installed: `node --version`
11
+ 3. Check that Claude Code CLI is available: `claude --version`
12
+ 4. Verify config exists: `ls -la ~/.codex-to-im/config.env`
13
+ 5. Check logs for startup errors in `~/.codex-to-im/logs/`
14
+
15
+ **Common causes**:
16
+ - Missing or invalid config.env -- open the local workbench and save a valid configuration
17
+ - Node.js not found or wrong version -- install Node.js >= 20
18
+ - Port or resource conflict -- check if another bridge instance is already running
19
+
20
+ ## Messages not received
21
+
22
+ **Symptoms**: Bot is online but doesn't respond to messages.
23
+
24
+ **Steps**:
25
+
26
+ 1. Verify the bot token is valid 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 Telegram: ensure you've sent `/start` to the bot first
29
+ 4. For Discord: verify the bot has been invited to the server with message read permissions
30
+ 5. For Feishu: confirm the app has been approved and event subscriptions are configured
31
+ 6. Check recent logs for incoming message events under `~/.codex-to-im/logs/`
32
+
33
+ ## Feishu streaming cards not working
34
+
35
+ **Symptoms**: Feishu responds only with a final message, or the streaming card never appears even though `Enable Feishu streaming response cards` is checked.
36
+
37
+ **Steps**:
38
+
39
+ 1. Check `~/.codex-to-im/logs/bridge.log` or `~/.claude-to-im/logs/bridge.log`
40
+ 2. Look for Feishu error `99991672`
41
+ 3. If the error mentions `cardkit:card:write`, add and publish the missing Feishu permissions:
42
+ - `cardkit:card:write`
43
+ - `cardkit:card:read`
44
+ - `im:message:update`
45
+ 4. Restart the bridge after the Feishu app version is approved
46
+
47
+ **Important behavior note**:
48
+
49
+ - If card creation is denied, the bridge falls back to a normal final-result message
50
+ - Even after the Feishu permissions are fixed, the current `codex` runtime usually delivers assistant text when the turn completes, not token-by-token
51
+ - That means Feishu streaming cards currently work best for `Thinking` / tool-progress updates, while the final response text may still appear all at once
52
+
53
+ ## Permission timeout
54
+
55
+ **Symptoms**: Claude Code session starts but times out waiting for tool approval.
56
+
57
+ **Steps**:
58
+
59
+ 1. The bridge runs Claude Code in non-interactive mode; ensure your Claude Code configuration allows the necessary tools
60
+ 2. Consider using `--allowedTools` in your configuration to pre-approve common tools
61
+ 3. Check network connectivity if the timeout occurs during API calls
62
+
63
+ ## High memory usage
64
+
65
+ **Symptoms**: The daemon process consumes increasing memory over time.
66
+
67
+ **Steps**:
68
+
69
+ 1. Check current bridge status from the local workbench
70
+ 2. Restart the daemon to reset memory:
71
+ ```
72
+ Stop the bridge, then start it again from the local workbench
73
+ ```
74
+ 3. If the issue persists, check how many concurrent sessions are active -- each Claude Code session consumes memory
75
+ 4. Review logs for error loops that may cause memory leaks
76
+
77
+ ## Stale PID file
78
+
79
+ **Symptoms**: Status shows "running" but the process doesn't exist, or start refuses because it thinks a daemon is already running.
80
+
81
+ The daemon management script (`daemon.sh`) handles stale PID files automatically. If you still encounter issues:
82
+
83
+ 1. Stop the bridge from the local workbench — this should clean up the stale PID file
84
+ 2. If stop also fails, manually remove the PID file:
85
+ ```bash
86
+ rm ~/.codex-to-im/runtime/bridge.pid
87
+ ```
88
+ 3. Start the bridge again from the local workbench
@@ -0,0 +1,46 @@
1
+ # Optional Codex Integration Usage
2
+
3
+ This repository no longer uses a full bridge-management skill as its primary workflow.
4
+
5
+ The main product is the local `codex-to-im` app and web workbench.
6
+
7
+ `SKILL.md` is only an optional Codex integration entry with two actions:
8
+
9
+ - `open`
10
+ - `share-feishu`
11
+
12
+ ## open
13
+
14
+ Open the local `codex-to-im` workbench.
15
+
16
+ Preferred command:
17
+
18
+ ```bash
19
+ codex-to-im open
20
+ ```
21
+
22
+ Fallback:
23
+
24
+ ```bash
25
+ node dist/cli.mjs open
26
+ ```
27
+
28
+ ## share-feishu
29
+
30
+ Open the Feishu handoff flow for the current workflow.
31
+
32
+ Preferred command:
33
+
34
+ ```bash
35
+ codex-to-im share-feishu
36
+ ```
37
+
38
+ Fallback:
39
+
40
+ ```bash
41
+ node dist/cli.mjs share-feishu
42
+ ```
43
+
44
+ ## Non-goals
45
+
46
+ If the user asks to configure credentials, start or stop the bridge, inspect logs, or diagnose problems, do not route them through the optional Codex integration. Open the local app instead.
@@ -0,0 +1,36 @@
1
+ import * as esbuild from 'esbuild';
2
+
3
+ const common = {
4
+ bundle: true,
5
+ platform: 'node',
6
+ format: 'esm',
7
+ target: 'node20',
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
+ '@openai/codex-sdk',
14
+ // discord.js optional native deps
15
+ 'bufferutil', 'utf-8-validate', 'zlib-sync', 'erlpack',
16
+ // Node.js built-ins
17
+ 'fs', 'path', 'os', 'crypto', 'http', 'https', 'net', 'tls',
18
+ 'stream', 'events', 'url', 'util', 'child_process', 'worker_threads',
19
+ 'node:*',
20
+ ],
21
+ banner: { js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);" },
22
+ };
23
+
24
+ async function build(entryPoint, outfile) {
25
+ await esbuild.build({
26
+ ...common,
27
+ entryPoints: [entryPoint],
28
+ outfile,
29
+ });
30
+ }
31
+
32
+ await build('src/main.ts', 'dist/daemon.mjs');
33
+ await build('src/ui-server.ts', 'dist/ui-server.mjs');
34
+ await build('src/cli.ts', 'dist/cli.mjs');
35
+
36
+ console.log('Built dist/daemon.mjs, dist/ui-server.mjs, dist/cli.mjs');
@@ -0,0 +1,16 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Windows entry point — delegates to supervisor-windows.ps1.
4
+ .DESCRIPTION
5
+ Usage: powershell -File scripts\daemon.ps1 start|stop|status|logs|install-service|uninstall-service
6
+ #>
7
+ param(
8
+ [Parameter(Position=0)]
9
+ [string]$Command = 'help',
10
+
11
+ [Parameter(Position=1)]
12
+ [int]$LogLines = 50
13
+ )
14
+
15
+ $supervisorScript = Join-Path (Split-Path -Parent $PSCommandPath) 'supervisor-windows.ps1'
16
+ & $supervisorScript $Command $LogLines
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ CTI_HOME="${CTI_HOME:-$HOME/.claude-to-im}"
4
+ SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
+ PID_FILE="$CTI_HOME/runtime/bridge.pid"
6
+ STATUS_FILE="$CTI_HOME/runtime/status.json"
7
+ LOG_FILE="$CTI_HOME/logs/bridge.log"
8
+
9
+ # ── Common helpers ──
10
+
11
+ ensure_dirs() { mkdir -p "$CTI_HOME"/{data,logs,runtime,data/messages}; }
12
+
13
+ ensure_built() {
14
+ local need_build=0
15
+ if [ ! -f "$SKILL_DIR/dist/daemon.mjs" ]; then
16
+ need_build=1
17
+ else
18
+ # Check if any source file is newer than the bundle
19
+ local newest_src
20
+ newest_src=$(find "$SKILL_DIR/src" -name '*.ts' -newer "$SKILL_DIR/dist/daemon.mjs" 2>/dev/null | head -1)
21
+ if [ -n "$newest_src" ]; then
22
+ need_build=1
23
+ fi
24
+ # Also check if node_modules/claude-to-im was updated (npm update)
25
+ # — its code is bundled into dist, so changes require a rebuild
26
+ if [ "$need_build" = "0" ] && [ -d "$SKILL_DIR/node_modules/claude-to-im/src" ]; then
27
+ local newest_dep
28
+ newest_dep=$(find "$SKILL_DIR/node_modules/claude-to-im/src" -name '*.ts' -newer "$SKILL_DIR/dist/daemon.mjs" 2>/dev/null | head -1)
29
+ if [ -n "$newest_dep" ]; then
30
+ need_build=1
31
+ fi
32
+ fi
33
+ fi
34
+ if [ "$need_build" = "1" ]; then
35
+ echo "Building daemon bundle..."
36
+ (cd "$SKILL_DIR" && npm run build)
37
+ fi
38
+ }
39
+
40
+ # Clean environment for subprocess isolation.
41
+ clean_env() {
42
+ unset CLAUDECODE 2>/dev/null || true
43
+
44
+ local runtime
45
+ runtime=$(grep "^CTI_RUNTIME=" "$CTI_HOME/config.env" 2>/dev/null | head -1 | cut -d= -f2- | tr -d "'" | tr -d '"' || true)
46
+ runtime="${runtime:-claude}"
47
+
48
+ local mode="${CTI_ENV_ISOLATION:-inherit}"
49
+ if [ "$mode" = "strict" ]; then
50
+ case "$runtime" in
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
+ }
69
+
70
+ read_pid() {
71
+ [ -f "$PID_FILE" ] && cat "$PID_FILE" 2>/dev/null || echo ""
72
+ }
73
+
74
+ pid_alive() {
75
+ local pid="$1"
76
+ [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null
77
+ }
78
+
79
+ status_running() {
80
+ [ -f "$STATUS_FILE" ] && grep -q '"running"[[:space:]]*:[[:space:]]*true' "$STATUS_FILE" 2>/dev/null
81
+ }
82
+
83
+ show_last_exit_reason() {
84
+ if [ -f "$STATUS_FILE" ]; then
85
+ local reason
86
+ reason=$(grep -o '"lastExitReason"[[:space:]]*:[[:space:]]*"[^"]*"' "$STATUS_FILE" 2>/dev/null | head -1 | sed 's/.*: *"//;s/"$//')
87
+ [ -n "$reason" ] && echo "Last exit reason: $reason"
88
+ fi
89
+ }
90
+
91
+ show_failure_help() {
92
+ echo ""
93
+ echo "Recent logs:"
94
+ tail -20 "$LOG_FILE" 2>/dev/null || echo " (no log file)"
95
+ echo ""
96
+ echo "Next steps:"
97
+ echo " 1. Run diagnostics: bash \"$SKILL_DIR/scripts/doctor.sh\""
98
+ echo " 2. Check full logs: bash \"$SKILL_DIR/scripts/daemon.sh\" logs 100"
99
+ echo " 3. Rebuild bundle: cd \"$SKILL_DIR\" && npm run build"
100
+ }
101
+
102
+ # ── Load platform-specific supervisor ──
103
+
104
+ case "$(uname -s)" in
105
+ Darwin)
106
+ # shellcheck source=supervisor-macos.sh
107
+ source "$SKILL_DIR/scripts/supervisor-macos.sh"
108
+ ;;
109
+ MINGW*|MSYS*|CYGWIN*)
110
+ # Windows detected via Git Bash / MSYS2 / Cygwin — delegate to PowerShell
111
+ echo "Windows detected. Delegating to supervisor-windows.ps1..."
112
+ powershell.exe -ExecutionPolicy Bypass -File "$SKILL_DIR/scripts/supervisor-windows.ps1" "$@"
113
+ exit $?
114
+ ;;
115
+ *)
116
+ # shellcheck source=supervisor-linux.sh
117
+ source "$SKILL_DIR/scripts/supervisor-linux.sh"
118
+ ;;
119
+ esac
120
+
121
+ # ── Commands ──
122
+
123
+ case "${1:-help}" in
124
+ start)
125
+ ensure_dirs
126
+ ensure_built
127
+
128
+ # Check if already running (supervisor-aware: launchctl on macOS, PID on Linux)
129
+ if supervisor_is_running; then
130
+ EXISTING_PID=$(read_pid)
131
+ echo "Bridge already running${EXISTING_PID:+ (PID: $EXISTING_PID)}"
132
+ cat "$STATUS_FILE" 2>/dev/null
133
+ exit 1
134
+ fi
135
+
136
+ # Source config.env BEFORE clean_env so that CTI_ANTHROPIC_PASSTHROUGH
137
+ # and other CTI_* flags are available when clean_env checks them.
138
+ [ -f "$CTI_HOME/config.env" ] && set -a && source "$CTI_HOME/config.env" && set +a
139
+
140
+ clean_env
141
+ echo "Starting bridge..."
142
+ supervisor_start
143
+
144
+ # Poll for up to 10 seconds waiting for status.json to report running
145
+ STARTED=false
146
+ for _ in $(seq 1 10); do
147
+ sleep 1
148
+ if status_running; then
149
+ STARTED=true
150
+ break
151
+ fi
152
+ # If supervisor process already died, stop waiting
153
+ if ! supervisor_is_running; then
154
+ break
155
+ fi
156
+ done
157
+
158
+ if [ "$STARTED" = "true" ]; then
159
+ NEW_PID=$(read_pid)
160
+ echo "Bridge started${NEW_PID:+ (PID: $NEW_PID)}"
161
+ cat "$STATUS_FILE" 2>/dev/null
162
+ else
163
+ echo "Failed to start bridge."
164
+ supervisor_is_running || echo " Process not running."
165
+ status_running || echo " status.json not reporting running=true."
166
+ show_last_exit_reason
167
+ show_failure_help
168
+ exit 1
169
+ fi
170
+ ;;
171
+
172
+ stop)
173
+ if supervisor_is_managed; then
174
+ echo "Stopping bridge..."
175
+ supervisor_stop
176
+ echo "Bridge stopped"
177
+ else
178
+ PID=$(read_pid)
179
+ if [ -z "$PID" ]; then echo "No bridge running"; exit 0; fi
180
+ if pid_alive "$PID"; then
181
+ kill "$PID"
182
+ for _ in $(seq 1 10); do
183
+ pid_alive "$PID" || break
184
+ sleep 1
185
+ done
186
+ pid_alive "$PID" && kill -9 "$PID"
187
+ echo "Bridge stopped"
188
+ else
189
+ echo "Bridge was not running (stale PID file)"
190
+ fi
191
+ rm -f "$PID_FILE"
192
+ fi
193
+ ;;
194
+
195
+ status)
196
+ # Platform-specific status info (prints launchd/service state)
197
+ supervisor_status_extra
198
+
199
+ # Process status: supervisor-aware (launchctl on macOS, PID on Linux)
200
+ if supervisor_is_running; then
201
+ PID=$(read_pid)
202
+ echo "Bridge process is running${PID:+ (PID: $PID)}"
203
+ # Business status from status.json
204
+ if status_running; then
205
+ echo "Bridge status: running"
206
+ else
207
+ echo "Bridge status: process alive but status.json not reporting running"
208
+ fi
209
+ cat "$STATUS_FILE" 2>/dev/null
210
+ else
211
+ echo "Bridge is not running"
212
+ [ -f "$PID_FILE" ] && rm -f "$PID_FILE"
213
+ show_last_exit_reason
214
+ fi
215
+ ;;
216
+
217
+ logs)
218
+ N="${2:-50}"
219
+ tail -n "$N" "$LOG_FILE" 2>/dev/null | sed -E 's/(token|secret|password)(["\\x27]?\s*[:=]\s*["\\x27]?)[^ "]+/\1\2*****/gi'
220
+ ;;
221
+
222
+ *)
223
+ echo "Usage: daemon.sh {start|stop|status|logs [N]}"
224
+ ;;
225
+ esac
@@ -0,0 +1,27 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Windows wrapper for the existing bash-based doctor script.
4
+ .DESCRIPTION
5
+ Prefers Git Bash / bash.exe when available. Falls back to a clear message
6
+ when bash is missing, because the full diagnostics live in doctor.sh.
7
+ #>
8
+
9
+ $ErrorActionPreference = 'Stop'
10
+
11
+ $doctorScript = Join-Path (Split-Path -Parent $PSCommandPath) 'doctor.sh'
12
+
13
+ if (-not (Test-Path $doctorScript)) {
14
+ Write-Error "doctor.sh not found at $doctorScript"
15
+ exit 1
16
+ }
17
+
18
+ $bash = Get-Command bash -ErrorAction SilentlyContinue
19
+ if (-not $bash) {
20
+ Write-Host "bash was not found in PATH."
21
+ Write-Host "Install Git Bash or another bash environment, then run:"
22
+ Write-Host " bash `"$doctorScript`""
23
+ exit 1
24
+ }
25
+
26
+ & $bash.Source $doctorScript
27
+ exit $LASTEXITCODE