@quantiya/codevibe-codex-plugin 1.0.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.
Files changed (68) hide show
  1. package/.env.example +29 -0
  2. package/README.md +155 -0
  3. package/bin/codevibe-codex +187 -0
  4. package/dist/approval-detector.d.ts +38 -0
  5. package/dist/approval-detector.d.ts.map +1 -0
  6. package/dist/approval-detector.js +174 -0
  7. package/dist/approval-detector.js.map +1 -0
  8. package/dist/appsync-client.d.ts +69 -0
  9. package/dist/appsync-client.d.ts.map +1 -0
  10. package/dist/appsync-client.js +937 -0
  11. package/dist/appsync-client.js.map +1 -0
  12. package/dist/auth-cli.d.ts +11 -0
  13. package/dist/auth-cli.d.ts.map +1 -0
  14. package/dist/auth-cli.js +241 -0
  15. package/dist/auth-cli.js.map +1 -0
  16. package/dist/config.d.ts +29 -0
  17. package/dist/config.d.ts.map +1 -0
  18. package/dist/config.js +116 -0
  19. package/dist/config.js.map +1 -0
  20. package/dist/crypto-service.d.ts +115 -0
  21. package/dist/crypto-service.d.ts.map +1 -0
  22. package/dist/crypto-service.js +278 -0
  23. package/dist/crypto-service.js.map +1 -0
  24. package/dist/event-mapper.d.ts +24 -0
  25. package/dist/event-mapper.d.ts.map +1 -0
  26. package/dist/event-mapper.js +268 -0
  27. package/dist/event-mapper.js.map +1 -0
  28. package/dist/key-manager.d.ts +87 -0
  29. package/dist/key-manager.d.ts.map +1 -0
  30. package/dist/key-manager.js +287 -0
  31. package/dist/key-manager.js.map +1 -0
  32. package/dist/logger.d.ts +2 -0
  33. package/dist/logger.d.ts.map +1 -0
  34. package/dist/logger.js +18 -0
  35. package/dist/logger.js.map +1 -0
  36. package/dist/prompt-parser.d.ts +3 -0
  37. package/dist/prompt-parser.d.ts.map +1 -0
  38. package/dist/prompt-parser.js +8 -0
  39. package/dist/prompt-parser.js.map +1 -0
  40. package/dist/prompt-responder.d.ts +18 -0
  41. package/dist/prompt-responder.d.ts.map +1 -0
  42. package/dist/prompt-responder.js +78 -0
  43. package/dist/prompt-responder.js.map +1 -0
  44. package/dist/server.d.ts +8 -0
  45. package/dist/server.d.ts.map +1 -0
  46. package/dist/server.js +1045 -0
  47. package/dist/server.js.map +1 -0
  48. package/dist/session-id-cache.d.ts +16 -0
  49. package/dist/session-id-cache.d.ts.map +1 -0
  50. package/dist/session-id-cache.js +90 -0
  51. package/dist/session-id-cache.js.map +1 -0
  52. package/dist/session-log-watcher.d.ts +61 -0
  53. package/dist/session-log-watcher.d.ts.map +1 -0
  54. package/dist/session-log-watcher.js +372 -0
  55. package/dist/session-log-watcher.js.map +1 -0
  56. package/dist/tmux-pane-observer.d.ts +39 -0
  57. package/dist/tmux-pane-observer.d.ts.map +1 -0
  58. package/dist/tmux-pane-observer.js +255 -0
  59. package/dist/tmux-pane-observer.js.map +1 -0
  60. package/dist/token-storage.d.ts +39 -0
  61. package/dist/token-storage.d.ts.map +1 -0
  62. package/dist/token-storage.js +169 -0
  63. package/dist/token-storage.js.map +1 -0
  64. package/dist/types.d.ts +158 -0
  65. package/dist/types.d.ts.map +1 -0
  66. package/dist/types.js +17 -0
  67. package/dist/types.js.map +1 -0
  68. package/package.json +72 -0
package/.env.example ADDED
@@ -0,0 +1,29 @@
1
+ # CodeVibe Codex Plugin Configuration
2
+ # Copy this file to .env.development for local development
3
+
4
+ # AWS AppSync (production values are embedded, only override for development)
5
+ # APPSYNC_URL=https://your-appsync-url.appsync-api.us-east-1.amazonaws.com/graphql
6
+
7
+ # Cognito Configuration (production values are embedded, only override for development)
8
+ # COGNITO_USER_POOL_ID=us-east-1_xxxxx
9
+ # COGNITO_CLIENT_ID=xxxxx
10
+ # COGNITO_DOMAIN=your-domain.auth.us-east-1.amazoncognito.com
11
+
12
+ # Server Configuration
13
+ PORT=3458
14
+ HOST=localhost
15
+
16
+ # Environment: 'production' (default) or 'development'
17
+ # ENVIRONMENT=development
18
+
19
+ # Logging
20
+ LOG_LEVEL=info
21
+ LOG_FILE=/tmp/codevibe-codex-mcp.log
22
+
23
+ # Codex CLI Configuration
24
+ CODEX_COMMAND=codex
25
+ CODEX_TIMEOUT=60000
26
+
27
+ # Approval Detection
28
+ # Timeout in ms before assuming Codex is waiting for approval
29
+ APPROVAL_TIMEOUT_MS=5000
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # CodeVibe Codex Plugin
2
+
3
+ Mobile companion plugin for OpenAI Codex CLI. Monitor and interact with your Codex sessions from your iPhone.
4
+
5
+ ## Overview
6
+
7
+ This plugin enables bidirectional synchronization between Codex CLI on your Mac and the CodeVibe iOS app. See your conversations, shell commands, file edits, and agent reasoning in real-time on your phone.
8
+
9
+ **Part of the CodeVibe multi-agent ecosystem:**
10
+ - [Claude Plugin](https://github.com/hendryyeh/quantiya-codevibe-claude-plugin) - For Claude Code
11
+ - [Gemini Plugin](https://github.com/hendryyeh/quantiya-codevibe-gemini-plugin) - For Gemini CLI
12
+ - **Codex Plugin** (this repo) - For OpenAI Codex CLI
13
+
14
+ ## Quick Start
15
+
16
+ ```bash
17
+ npm install -g codevibe-codex-plugin
18
+ codevibe-codex login # authenticate with CodeVibe backend
19
+ codevibe-codex # start Codex with mobile sync
20
+ ```
21
+
22
+ ## Features
23
+
24
+ | Feature | Supported |
25
+ |---------|-----------|
26
+ | User prompts | Yes |
27
+ | Assistant responses | Yes |
28
+ | Agent reasoning/thinking | Yes |
29
+ | Shell commands | Yes |
30
+ | File edits (patches) | Yes |
31
+ | Tool outputs | Yes |
32
+ | Image attachments | Yes |
33
+ | Approval prompts | Yes (via tmux pane detection) |
34
+ | File diff preview | Yes |
35
+
36
+ ### What gets synced
37
+
38
+ - **Desktop → Mobile:** user prompts, assistant responses, shell commands, tool/file edits, approval prompts, images, logs
39
+ - **Mobile → Desktop:** text prompts and approval responses sent from iOS app into the active Codex session (via tmux)
40
+
41
+ ## Prerequisites
42
+
43
+ - macOS
44
+ - Node.js 18+
45
+ - tmux (`brew install tmux`)
46
+ - [Codex CLI](https://github.com/openai/codex) installed and authenticated
47
+ - CodeVibe iOS app
48
+
49
+ ## Installation
50
+
51
+ ```bash
52
+ npm install -g codevibe-codex-plugin
53
+ ```
54
+
55
+ ### From Source (Development)
56
+
57
+ ```bash
58
+ git clone https://github.com/hendryyeh/quantiya-codevibe-codex-plugin.git
59
+ cd codevibe-codex-plugin
60
+ npm install
61
+ npm run build
62
+ ```
63
+
64
+ ## Usage
65
+
66
+ Use `codevibe-codex` instead of `codex` to enable mobile sync:
67
+
68
+ ```bash
69
+ # Start Codex with mobile sync
70
+ codevibe-codex
71
+
72
+ # Start with an initial prompt
73
+ codevibe-codex "fix the bug in auth.ts"
74
+ ```
75
+
76
+ ### CLI Commands
77
+
78
+ ```bash
79
+ codevibe-codex login # Authenticate with CodeVibe backend
80
+ codevibe-codex status # Check authentication status
81
+ codevibe-codex logout # Sign out
82
+ codevibe-codex # Start Codex with mobile sync
83
+ ```
84
+
85
+ ## How It Works
86
+
87
+ 1. **Session Log Watching** - Monitors `~/.codex/sessions/` for JSONL log files
88
+ 2. **Real-time Sync** - Parses log entries and syncs to AWS AppSync backend
89
+ 3. **Mobile Display** - iOS app receives events via WebSocket subscription
90
+ 4. **Mobile Input** - Messages from iOS are sent to terminal via tmux
91
+
92
+ Each live Codex process appears as its own CodeVibe session, even if multiple Codex sessions are running in the same project directory.
93
+
94
+ ### Data Flow
95
+
96
+ ```
97
+ Desktop → Mobile:
98
+ Codex CLI → Session JSONL → Plugin → AppSync → iOS App
99
+
100
+ Mobile → Desktop:
101
+ iOS App → AppSync → Plugin → tmux send-keys → Codex CLI
102
+ ```
103
+
104
+ ## Configuration
105
+
106
+ Create a `.env.development` file (see `.env.example`):
107
+
108
+ ```bash
109
+ # AWS AppSync
110
+ APPSYNC_URL=https://xxx.appsync-api.us-east-1.amazonaws.com/graphql
111
+ APPSYNC_REALTIME_URL=wss://xxx.appsync-realtime-api.us-east-1.amazonaws.com/graphql
112
+
113
+ # Cognito
114
+ COGNITO_USER_POOL_ID=us-east-1_xxx
115
+ COGNITO_CLIENT_ID=xxx
116
+ COGNITO_DOMAIN=xxx.auth.us-east-1.amazoncognito.com
117
+ ```
118
+
119
+ ## Troubleshooting
120
+
121
+ ### Session not appearing in iOS app
122
+
123
+ ```bash
124
+ # Check if session logs exist
125
+ ls -la ~/.codex/sessions/$(date +%Y)/$(date +%m)/$(date +%d)/
126
+
127
+ # Check plugin logs
128
+ tail -f /tmp/codevibe-codex-mcp.log
129
+ ```
130
+
131
+ ### Mobile prompts not working
132
+
133
+ ```bash
134
+ # Verify tmux session exists
135
+ tmux list-sessions | grep codevibe-codex
136
+ ```
137
+
138
+ ## Known Limitations
139
+
140
+ - **Approval prompts are not in JSONL logs** - Detected via tmux pane observation instead of structured log entries
141
+ - **macOS only** - Requires tmux for terminal integration
142
+
143
+ ## Related
144
+
145
+ - **CodeVibe iOS App** - Available on the App Store
146
+ - [Claude Plugin](https://github.com/hendryyeh/codevibe-claude-plugin) - For Claude Code
147
+ - [Gemini Plugin](https://github.com/hendryyeh/codevibe-gemini-plugin) - For Gemini CLI
148
+
149
+ ## License
150
+
151
+ MIT
152
+
153
+ ---
154
+
155
+ **Maintained by:** CodeVibe Team
@@ -0,0 +1,187 @@
1
+ #!/bin/bash
2
+ #
3
+ # codevibe-codex - Wrapper to run Codex CLI inside tmux for mobile control
4
+ #
5
+ # This script launches Codex CLI inside a tmux session, enabling:
6
+ # - Mobile prompts when screen is locked (via tmux send-keys)
7
+ # - Same user experience as regular codex command
8
+ # - Automatic sync with iOS app via session log watching
9
+ #
10
+ # Usage:
11
+ # codevibe-codex [codex-args...]
12
+ # codevibe-codex login # Sign in via browser
13
+ # codevibe-codex logout # Sign out
14
+ # codevibe-codex status # Show auth status
15
+ #
16
+ # Environment:
17
+ # ENVIRONMENT # Set to 'production' (default) or 'development'
18
+ #
19
+ # Examples:
20
+ # codevibe-codex # Start new session
21
+ # codevibe-codex "fix the bug" # Start with prompt
22
+ # ENVIRONMENT=development codevibe-codex login # Login to development
23
+ #
24
+
25
+ set -e
26
+
27
+ # Default to production environment if not specified
28
+ export ENVIRONMENT="${ENVIRONMENT:-production}"
29
+
30
+ # Use TMPDIR if set (macOS sets this to user-specific temp), otherwise /tmp
31
+ CODEVIBE_TMPDIR="${TMPDIR:-/tmp}"
32
+
33
+ # Get the directory where this script is located (resolving symlinks)
34
+ # This is needed because npm global installs symlink bin scripts to /usr/local/bin/
35
+ SOURCE="${BASH_SOURCE[0]}"
36
+ while [ -L "$SOURCE" ]; do
37
+ DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
38
+ SOURCE="$(readlink "$SOURCE")"
39
+ [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
40
+ done
41
+ SCRIPT_DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
42
+ PLUGIN_DIR="$(dirname "$SCRIPT_DIR")"
43
+
44
+ # Handle auth commands (login, logout, status, reset-device)
45
+ # Delegate to codevibe-core CLI (shared auth across all plugins)
46
+ case "$1" in
47
+ login|logout|status|reset-device)
48
+ CORE_CLI="$PLUGIN_DIR/node_modules/@quantiya/codevibe-core/bin/codevibe.js"
49
+ if [ -f "$CORE_CLI" ]; then
50
+ exec node "$CORE_CLI" "$1"
51
+ else
52
+ echo "Error: codevibe-core not found. Run 'npm install' in the plugin directory first."
53
+ exit 1
54
+ fi
55
+ ;;
56
+ esac
57
+
58
+ # Configuration
59
+ TMUX_SESSION_PREFIX="codevibe-codex"
60
+ LOG_FILE="${CODEVIBE_TMPDIR}/codevibe-codex-wrapper.log"
61
+ MCP_LOG_FILE="${CODEVIBE_TMPDIR}/codevibe-codex-mcp.log"
62
+
63
+ log() {
64
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
65
+ }
66
+
67
+ # Cleanup function to kill server when wrapper exits
68
+ cleanup() {
69
+ log "Cleanup triggered"
70
+ if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
71
+ log "Stopping server (PID: $SERVER_PID)"
72
+ kill "$SERVER_PID" 2>/dev/null || true
73
+ wait "$SERVER_PID" 2>/dev/null || true
74
+ fi
75
+ # Remove PID file
76
+ rm -f "${CODEVIBE_TMPDIR}/codevibe-codex-server-$$.pid"
77
+ }
78
+
79
+ # Set up trap for cleanup
80
+ trap cleanup EXIT INT TERM
81
+
82
+ # Check if tmux is installed
83
+ if ! command -v tmux &> /dev/null; then
84
+ echo "Error: tmux is required but not installed."
85
+ echo "Install with: brew install tmux"
86
+ exit 1
87
+ fi
88
+
89
+ # Check if codex is installed
90
+ if ! command -v codex &> /dev/null; then
91
+ echo "Error: codex CLI is not installed."
92
+ echo "Install with: npm install -g @openai/codex"
93
+ exit 1
94
+ fi
95
+
96
+ # Check if node is installed
97
+ if ! command -v node &> /dev/null; then
98
+ echo "Error: Node.js is required but not installed."
99
+ exit 1
100
+ fi
101
+
102
+ # Check if server is built
103
+ if [ ! -f "$PLUGIN_DIR/dist/server.js" ]; then
104
+ echo "Error: Server not built. Run 'npm run build' in the plugin directory first."
105
+ exit 1
106
+ fi
107
+
108
+ # Generate a unique session name
109
+ SESSION_NAME="${TMUX_SESSION_PREFIX}-$$"
110
+ WORKING_DIR="$(pwd)"
111
+
112
+ log "Starting codevibe-codex with session: $SESSION_NAME"
113
+ log "Working directory: $WORKING_DIR"
114
+ log "Arguments: $*"
115
+
116
+ # Check if we're already inside tmux
117
+ if [ -n "$TMUX" ]; then
118
+ log "Already inside tmux, running codex directly"
119
+ exec codex "$@"
120
+ fi
121
+
122
+ # Check if running in a terminal
123
+ if [ ! -t 0 ] || [ ! -t 1 ]; then
124
+ log "Not running in a terminal, running codex directly"
125
+ exec codex "$@"
126
+ fi
127
+
128
+ # Start the session log watcher server in background
129
+ log "Starting session log watcher server..."
130
+ export CODEX_WORKING_DIRECTORY="$WORKING_DIR"
131
+ export CODEVIBE_CODEX_TMUX_SESSION="$SESSION_NAME"
132
+
133
+ # Start server and capture its PID
134
+ node "$PLUGIN_DIR/dist/server.js" >> "$MCP_LOG_FILE" 2>&1 &
135
+ SERVER_PID=$!
136
+ echo "$SERVER_PID" > "${CODEVIBE_TMPDIR}/codevibe-codex-server-$$.pid"
137
+
138
+ log "Server started with PID: $SERVER_PID"
139
+
140
+ # Wait a moment for server to initialize
141
+ sleep 1
142
+
143
+ # Check if server is still running (exits if auth failed)
144
+ if ! kill -0 "$SERVER_PID" 2>/dev/null; then
145
+ log "ERROR: Server failed to start"
146
+ # Show the last few lines of the log for context (e.g., auth error)
147
+ echo ""
148
+ tail -3 "$MCP_LOG_FILE" 2>/dev/null | grep -v '^\[' | head -1
149
+ echo ""
150
+ echo "Server failed to start. Check $MCP_LOG_FILE for details."
151
+ exit 1
152
+ fi
153
+
154
+ # Create tmux session and run codex
155
+ log "Creating tmux session: $SESSION_NAME"
156
+
157
+ # Build the codex command with proper escaping
158
+ CODEX_CMD="codex"
159
+ for arg in "$@"; do
160
+ # Escape single quotes in arguments
161
+ escaped_arg=$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")
162
+ CODEX_CMD="$CODEX_CMD '$escaped_arg'"
163
+ done
164
+
165
+ # Create the session running codex
166
+ tmux new-session -d -s "$SESSION_NAME" -x "$(tput cols)" -y "$(tput lines)" \
167
+ "export CODEVIBE_CODEX_TMUX_SESSION='$SESSION_NAME'; export ENVIRONMENT='$ENVIRONMENT'; $CODEX_CMD; exit"
168
+
169
+ # Enable mouse support for scrolling
170
+ tmux set-option -t "$SESSION_NAME" -g mouse on
171
+
172
+ # Enable copy/paste with system clipboard (macOS)
173
+ tmux set-option -t "$SESSION_NAME" set-clipboard on
174
+ tmux set-window-option -t "$SESSION_NAME" mode-keys vi
175
+ tmux bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"
176
+ tmux bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"
177
+
178
+ # Store session mapping for mobile prompts
179
+ echo "$SESSION_NAME" > "${CODEVIBE_TMPDIR}/codevibe-codex-tmux-session-$$"
180
+
181
+ log "Attaching to tmux session: $SESSION_NAME"
182
+
183
+ # Attach to the session
184
+ tmux attach-session -t "$SESSION_NAME"
185
+
186
+ # After tmux exits, cleanup is handled by trap
187
+ log "Tmux session ended"
@@ -0,0 +1,38 @@
1
+ import { EventEmitter } from 'events';
2
+ import { PendingToolCall } from './types';
3
+ /**
4
+ * Detects when Codex might be waiting for approval using timeout heuristic.
5
+ * Emits an event with contextual metadata (file path, diff, parsed input) so the
6
+ * mobile client can show an interactive prompt with details.
7
+ */
8
+ export declare class ApprovalDetector extends EventEmitter {
9
+ private pendingCalls;
10
+ private timers;
11
+ private timeoutMs;
12
+ constructor();
13
+ /**
14
+ * Called when a tool call starts.
15
+ */
16
+ onToolCallStart(callId: string, name: string, input: string): void;
17
+ /**
18
+ * Called when a tool call completes (output received).
19
+ */
20
+ onToolCallComplete(callId: string): void;
21
+ /**
22
+ * Check if a call is still pending after timeout.
23
+ */
24
+ private checkPendingCall;
25
+ /**
26
+ * Extract a user-facing hint for the pending approval.
27
+ */
28
+ private extractHint;
29
+ private mapToolName;
30
+ private parseInput;
31
+ private extractFilePath;
32
+ private extractDiff;
33
+ getPendingCalls(): PendingToolCall[];
34
+ hasPendingCalls(): boolean;
35
+ clear(): void;
36
+ shutdown(): void;
37
+ }
38
+ //# sourceMappingURL=approval-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-detector.d.ts","sourceRoot":"","sources":["../src/approval-detector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,SAAS,CAAS;;IAQ1B;;OAEG;IACI,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IA4BzE;;OAEG;IACI,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAY/C;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA4BxB;;OAEG;IACH,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,WAAW;IAYZ,eAAe,IAAI,eAAe,EAAE;IAIpC,eAAe,IAAI,OAAO;IAI1B,KAAK,IAAI,IAAI;IASb,QAAQ,IAAI,IAAI;CAKxB"}
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApprovalDetector = void 0;
4
+ const events_1 = require("events");
5
+ const codevibe_core_1 = require("@quantiya/codevibe-core");
6
+ const logger_1 = require("./logger");
7
+ /**
8
+ * Detects when Codex might be waiting for approval using timeout heuristic.
9
+ * Emits an event with contextual metadata (file path, diff, parsed input) so the
10
+ * mobile client can show an interactive prompt with details.
11
+ */
12
+ class ApprovalDetector extends events_1.EventEmitter {
13
+ constructor() {
14
+ super();
15
+ this.pendingCalls = new Map();
16
+ this.timers = new Map();
17
+ this.timeoutMs = (0, codevibe_core_1.getConfig)().codex.approvalTimeoutMs;
18
+ logger_1.logger.info('Approval detector initialized', { timeoutMs: this.timeoutMs });
19
+ }
20
+ /**
21
+ * Called when a tool call starts.
22
+ */
23
+ onToolCallStart(callId, name, input) {
24
+ logger_1.logger.debug('Tool call started', { callId, name });
25
+ // Parse input for extra context (file path, diff, parsed JSON)
26
+ const parsedInput = this.parseInput(input);
27
+ const filePath = this.extractFilePath(name, input, parsedInput);
28
+ const diff = this.extractDiff(name, input, parsedInput);
29
+ const pending = {
30
+ callId,
31
+ name,
32
+ input,
33
+ filePath,
34
+ diff,
35
+ parsedInput,
36
+ timestamp: Date.now(),
37
+ notificationSent: false,
38
+ };
39
+ this.pendingCalls.set(callId, pending);
40
+ const timer = setTimeout(() => {
41
+ this.checkPendingCall(callId);
42
+ }, this.timeoutMs);
43
+ this.timers.set(callId, timer);
44
+ }
45
+ /**
46
+ * Called when a tool call completes (output received).
47
+ */
48
+ onToolCallComplete(callId) {
49
+ logger_1.logger.debug('Tool call completed', { callId });
50
+ this.pendingCalls.delete(callId);
51
+ const timer = this.timers.get(callId);
52
+ if (timer) {
53
+ clearTimeout(timer);
54
+ this.timers.delete(callId);
55
+ }
56
+ }
57
+ /**
58
+ * Check if a call is still pending after timeout.
59
+ */
60
+ checkPendingCall(callId) {
61
+ const pending = this.pendingCalls.get(callId);
62
+ if (!pending)
63
+ return;
64
+ if (pending.notificationSent)
65
+ return;
66
+ const elapsed = Date.now() - pending.timestamp;
67
+ logger_1.logger.info('Tool call still pending after timeout', {
68
+ callId,
69
+ name: pending.name,
70
+ elapsedMs: elapsed,
71
+ });
72
+ pending.notificationSent = true;
73
+ this.pendingCalls.set(callId, pending);
74
+ this.emit('approval-pending', {
75
+ callId,
76
+ toolName: pending.name,
77
+ hint: this.extractHint(pending.name, pending.input, pending.filePath),
78
+ filePath: pending.filePath,
79
+ diff: pending.diff,
80
+ toolInput: pending.parsedInput,
81
+ rawInput: pending.input,
82
+ elapsedMs: elapsed,
83
+ });
84
+ }
85
+ /**
86
+ * Extract a user-facing hint for the pending approval.
87
+ */
88
+ extractHint(name, input, filePath) {
89
+ if (filePath) {
90
+ return `File: ${filePath}`;
91
+ }
92
+ if (name === 'apply_patch' && input) {
93
+ const match = input.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);
94
+ if (match) {
95
+ return `File: ${match[1].trim()}`;
96
+ }
97
+ }
98
+ if (name === 'shell_command' || name === 'shell') {
99
+ try {
100
+ const args = JSON.parse(input);
101
+ if (args.command) {
102
+ const cmd = args.command.substring(0, 50);
103
+ return `Command: ${cmd}${args.command.length > 50 ? '...' : ''}`;
104
+ }
105
+ }
106
+ catch {
107
+ // Ignore parse errors
108
+ }
109
+ }
110
+ return `Tool: ${this.mapToolName(name)}`;
111
+ }
112
+ mapToolName(name) {
113
+ const mapping = {
114
+ shell_command: 'Bash',
115
+ shell: 'Bash',
116
+ apply_patch: 'File Edit',
117
+ write_file: 'Write File',
118
+ read_file: 'Read File',
119
+ };
120
+ return mapping[name] || name;
121
+ }
122
+ parseInput(input) {
123
+ if (!input)
124
+ return undefined;
125
+ try {
126
+ return JSON.parse(input);
127
+ }
128
+ catch {
129
+ return undefined;
130
+ }
131
+ }
132
+ extractFilePath(name, rawInput, parsedInput) {
133
+ if (name === 'apply_patch' && rawInput) {
134
+ const match = rawInput.match(/\*\*\* (?:Update|Add|Delete) File: (.+)/);
135
+ if (match)
136
+ return match[1].trim();
137
+ }
138
+ const candidate = parsedInput?.file_path || parsedInput?.path || parsedInput?.filePath;
139
+ if (candidate && typeof candidate === 'string') {
140
+ return candidate;
141
+ }
142
+ return undefined;
143
+ }
144
+ extractDiff(name, rawInput, parsedInput) {
145
+ if (name === 'apply_patch' && rawInput) {
146
+ return rawInput;
147
+ }
148
+ if (parsedInput?.diff && typeof parsedInput.diff === 'string') {
149
+ return parsedInput.diff;
150
+ }
151
+ return undefined;
152
+ }
153
+ getPendingCalls() {
154
+ return Array.from(this.pendingCalls.values());
155
+ }
156
+ hasPendingCalls() {
157
+ return this.pendingCalls.size > 0;
158
+ }
159
+ clear() {
160
+ for (const timer of this.timers.values()) {
161
+ clearTimeout(timer);
162
+ }
163
+ this.timers.clear();
164
+ this.pendingCalls.clear();
165
+ logger_1.logger.debug('Approval detector cleared');
166
+ }
167
+ shutdown() {
168
+ this.clear();
169
+ this.removeAllListeners();
170
+ logger_1.logger.info('Approval detector shutdown');
171
+ }
172
+ }
173
+ exports.ApprovalDetector = ApprovalDetector;
174
+ //# sourceMappingURL=approval-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-detector.js","sourceRoot":"","sources":["../src/approval-detector.ts"],"names":[],"mappings":";;;AAAA,mCAAsC;AACtC,2DAAoD;AACpD,qCAAkC;AAGlC;;;;GAIG;AACH,MAAa,gBAAiB,SAAQ,qBAAY;IAKhD;QACE,KAAK,EAAE,CAAC;QALF,iBAAY,GAAiC,IAAI,GAAG,EAAE,CAAC;QACvD,WAAM,GAAgC,IAAI,GAAG,EAAE,CAAC;QAKtD,IAAI,CAAC,SAAS,GAAG,IAAA,yBAAS,GAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC;QACrD,eAAM,CAAC,IAAI,CAAC,+BAA+B,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;IAC9E,CAAC;IAED;;OAEG;IACI,eAAe,CAAC,MAAc,EAAE,IAAY,EAAE,KAAa;QAChE,eAAM,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAEpD,+DAA+D;QAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;QAChE,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC;QAExD,MAAM,OAAO,GAAoB;YAC/B,MAAM;YACN,IAAI;YACJ,KAAK;YACL,QAAQ;YACR,IAAI;YACJ,WAAW;YACX,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,gBAAgB,EAAE,KAAK;SACxB,CAAC;QAEF,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAEvC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAEnB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACI,kBAAkB,CAAC,MAAc;QACtC,eAAM,CAAC,KAAK,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAEhD,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAEjC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED;;OAEG;IACK,gBAAgB,CAAC,MAAc;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAE9C,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,IAAI,OAAO,CAAC,gBAAgB;YAAE,OAAO;QAErC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;QAC/C,eAAM,CAAC,IAAI,CAAC,uCAAuC,EAAE;YACnD,MAAM;YACN,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,SAAS,EAAE,OAAO;SACnB,CAAC,CAAC;QAEH,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;QAChC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAEvC,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE;YAC5B,MAAM;YACN,QAAQ,EAAE,OAAO,CAAC,IAAI;YACtB,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC;YACrE,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,SAAS,EAAE,OAAO,CAAC,WAAW;YAC9B,QAAQ,EAAE,OAAO,CAAC,KAAK;YACvB,SAAS,EAAE,OAAO;SACnB,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,WAAW,CAAC,IAAY,EAAE,KAAa,EAAE,QAAiB;QAChE,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,SAAS,QAAQ,EAAE,CAAC;QAC7B,CAAC;QAED,IAAI,IAAI,KAAK,aAAa,IAAI,KAAK,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YACrE,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,CAAC;QACH,CAAC;QAED,IAAI,IAAI,KAAK,eAAe,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACjD,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBAC/B,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBACjB,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC1C,OAAO,YAAY,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACnE,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,sBAAsB;YACxB,CAAC;QACH,CAAC;QAED,OAAO,SAAS,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;IAC3C,CAAC;IAEO,WAAW,CAAC,IAAY;QAC9B,MAAM,OAAO,GAA2B;YACtC,aAAa,EAAE,MAAM;YACrB,KAAK,EAAE,MAAM;YACb,WAAW,EAAE,WAAW;YACxB,UAAU,EAAE,YAAY;YACxB,SAAS,EAAE,WAAW;SACvB,CAAC;QACF,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAC/B,CAAC;IAEO,UAAU,CAAC,KAAa;QAC9B,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAEO,eAAe,CAAC,IAAY,EAAE,QAAgB,EAAE,WAAiB;QACvE,IAAI,IAAI,KAAK,aAAa,IAAI,QAAQ,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;YACxE,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACpC,CAAC;QAED,MAAM,SAAS,GAAG,WAAW,EAAE,SAAS,IAAI,WAAW,EAAE,IAAI,IAAI,WAAW,EAAE,QAAQ,CAAC;QACvF,IAAI,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;YAC/C,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAEO,WAAW,CAAC,IAAY,EAAE,QAAgB,EAAE,WAAiB;QACnE,IAAI,IAAI,KAAK,aAAa,IAAI,QAAQ,EAAE,CAAC;YACvC,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,IAAI,WAAW,EAAE,IAAI,IAAI,OAAO,WAAW,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC9D,OAAO,WAAW,CAAC,IAAI,CAAC;QAC1B,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAEM,eAAe;QACpB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC;IAChD,CAAC;IAEM,eAAe;QACpB,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,CAAC;IACpC,CAAC;IAEM,KAAK;QACV,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;YACzC,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAC1B,eAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC5C,CAAC;IAEM,QAAQ;QACb,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,eAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IAC5C,CAAC;CACF;AA1LD,4CA0LC"}
@@ -0,0 +1,69 @@
1
+ import { CreateEventInput, CreateSessionInput, UpdateSessionInput, UpdateEventStatusInput, CreateFileChangeInput, Event, Session, EventSource, DownloadUrlResponse } from './types';
2
+ export declare class AppSyncClient {
3
+ private authenticated;
4
+ private currentUserId;
5
+ private currentEmail;
6
+ private storedTokens;
7
+ private activeSubscriptions;
8
+ constructor();
9
+ getCurrentUserId(): string;
10
+ getCurrentUserEmail(): string | null;
11
+ /**
12
+ * Authenticate using stored OAuth tokens from 'codevibe-codex login'
13
+ * Returns true if successfully authenticated, false otherwise
14
+ */
15
+ authenticateWithStoredTokens(): Promise<boolean>;
16
+ /**
17
+ * Refresh expired tokens using the refresh token
18
+ */
19
+ private refreshStoredTokens;
20
+ /**
21
+ * Check if stored tokens exist and are valid
22
+ */
23
+ hasValidStoredTokens(): boolean;
24
+ signOutUser(): void;
25
+ /**
26
+ * Make a direct GraphQL request to AppSync using fetch
27
+ * Uses stored OAuth tokens for Cognito User Pool authentication
28
+ * Automatically refreshes token and retries on 401 errors
29
+ */
30
+ private graphqlRequest;
31
+ createSession(input: CreateSessionInput): Promise<Session>;
32
+ updateSession(input: UpdateSessionInput): Promise<Session>;
33
+ createEvent(input: CreateEventInput): Promise<Event>;
34
+ updateEventStatus(input: UpdateEventStatusInput): Promise<Event>;
35
+ createFileChange(input: CreateFileChangeInput): Promise<any>;
36
+ getSession(sessionId: string): Promise<Session | null>;
37
+ listEvents(sessionId: string, source?: EventSource): Promise<Event[]>;
38
+ subscribeToEvents(sessionId: string, onEvent: (event: Event) => void, onError?: (error: Error) => void): () => void;
39
+ /**
40
+ * Build the AppSync real-time WebSocket URL with authorization
41
+ */
42
+ private buildRealtimeUrl;
43
+ /**
44
+ * Create a custom WebSocket subscription to AppSync
45
+ * This bypasses Amplify which doesn't work with externally stored tokens
46
+ */
47
+ private createSubscription;
48
+ /**
49
+ * Send subscription start message
50
+ */
51
+ private sendSubscriptionStart;
52
+ /**
53
+ * Reset the keep-alive timer - if no message received in 5 minutes, reconnect
54
+ */
55
+ private resetKeepAliveTimer;
56
+ private handleSubscriptionError;
57
+ private cleanupSubscriptionState;
58
+ cleanupSubscriptions(): void;
59
+ getAttachmentDownloadUrl(s3Key: string): Promise<DownloadUrlResponse>;
60
+ isAuthenticated(): boolean;
61
+ registerDeviceKey(deviceId: string, publicKey: string, platform: string, deviceName?: string): Promise<void>;
62
+ removeDeviceKey(deviceId: string): Promise<void>;
63
+ listUserDeviceKeys(): Promise<Array<{
64
+ deviceId: string;
65
+ publicKey: string;
66
+ }>>;
67
+ grantSessionKey(sessionId: string, deviceId: string, encryptedKey: string, ephemeralPublicKey: string): Promise<void>;
68
+ }
69
+ //# sourceMappingURL=appsync-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appsync-client.d.ts","sourceRoot":"","sources":["../src/appsync-client.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,EACL,OAAO,EACP,WAAW,EAEX,mBAAmB,EACpB,MAAM,SAAS,CAAC;AA0OjB,qBAAa,aAAa;IACxB,OAAO,CAAC,aAAa,CAAkB;IACvC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,mBAAmB,CAA6C;;IAOjE,gBAAgB,IAAI,MAAM;IAK1B,mBAAmB,IAAI,MAAM,GAAG,IAAI;IAI3C;;;OAGG;IACU,4BAA4B,IAAI,OAAO,CAAC,OAAO,CAAC;IA2C7D;;OAEG;YACW,mBAAmB;IAoDjC;;OAEG;IACI,oBAAoB,IAAI,OAAO;IAM/B,WAAW,IAAI,IAAI;IAQ1B;;;;OAIG;YACW,cAAc;IAqDf,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAwC1D,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAsB1D,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,KAAK,CAAC;IA8BpD,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,KAAK,CAAC;IAuBhE,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC;IA6B5D,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IActD,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAc3E,iBAAiB,CACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,EAC/B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAC/B,MAAM,IAAI;IAoCb;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAuBxB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAuI1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA8B7B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,uBAAuB;IAoE/B,OAAO,CAAC,wBAAwB;IAgCzB,oBAAoB,IAAI,IAAI;IActB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAmB3E,eAAe,IAAI,OAAO;IAOpB,iBAAiB,CAC5B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;IAuBH,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAchD,kBAAkB,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAqB7E,eAAe,CAC1B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,IAAI,CAAC;CAmBjB"}