zenflo 0.11.5 → 0.11.7
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/README.md +419 -37
- package/dist/codex/zenfloMcpStdioBridge.cjs +2 -2
- package/dist/codex/zenfloMcpStdioBridge.mjs +2 -2
- package/dist/{index-6-qKdQ7W.cjs → index-HBSmEvnF.cjs} +83 -46
- package/dist/{index-DuOY65WC.mjs → index-yJG0qz0r.mjs} +82 -45
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +28 -13
- package/dist/lib.d.mts +28 -13
- package/dist/lib.mjs +1 -1
- package/dist/{runCodex-hZGg21lA.mjs → runCodex-Bl0T0A2Z.mjs} +23 -6
- package/dist/{runCodex-qe9_J04U.cjs → runCodex-cXLrsovg.cjs} +23 -6
- package/dist/{types-ROQJQZrv.mjs → types-DJhJK2jD.mjs} +39 -30
- package/dist/{types-BowvGBcM.cjs → types-Dvhor4zW.cjs} +40 -31
- package/package.json +1 -1
- package/scripts/claude_local_launcher.cjs +262 -5
- package/scripts/extension_wrapper.sh +13 -0
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
1
3
|
const crypto = require('crypto');
|
|
2
4
|
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { execSync, spawn } = require('child_process');
|
|
3
7
|
|
|
4
8
|
// Disable autoupdater (never works really)
|
|
5
9
|
process.env.DISABLE_AUTOUPDATER = '1';
|
|
6
10
|
|
|
11
|
+
// Debug helper - only output when DEBUG=1
|
|
12
|
+
const DEBUG = process.env.DEBUG === '1' || process.env.DEBUG === 'true';
|
|
13
|
+
function debug(...args) {
|
|
14
|
+
if (DEBUG) {
|
|
15
|
+
console.error(...args);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
// Helper to write JSON messages to fd 3
|
|
8
20
|
function writeMessage(message) {
|
|
9
21
|
try {
|
|
@@ -13,6 +25,145 @@ function writeMessage(message) {
|
|
|
13
25
|
}
|
|
14
26
|
}
|
|
15
27
|
|
|
28
|
+
// Check if we're being called from zenflo (has fd 3 open) or directly from Claude Code extension
|
|
29
|
+
let isCalledFromZenflo = false;
|
|
30
|
+
try {
|
|
31
|
+
// Try to write to fd 3 - if it exists, we're being called from zenflo
|
|
32
|
+
fs.writeSync(3, '');
|
|
33
|
+
isCalledFromZenflo = true;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
// fd 3 doesn't exist, we're being called directly from Claude Code extension
|
|
36
|
+
isCalledFromZenflo = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Track session IDs to prevent duplicate notifications
|
|
40
|
+
const capturedSessionIds = new Set();
|
|
41
|
+
let daemonNotified = false;
|
|
42
|
+
let sessionWatcher = null;
|
|
43
|
+
|
|
44
|
+
// Helper to get project path (same logic as zenflo)
|
|
45
|
+
function getProjectPath(workingDirectory) {
|
|
46
|
+
const { join, resolve } = require('path');
|
|
47
|
+
const { homedir } = require('os');
|
|
48
|
+
|
|
49
|
+
// Resolve and convert to a filesystem-safe path (replace /, \, ., : with -)
|
|
50
|
+
const projectId = resolve(workingDirectory).replace(/[\\\/\.:]/g, '-');
|
|
51
|
+
|
|
52
|
+
// Get Claude config directory
|
|
53
|
+
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
|
|
54
|
+
|
|
55
|
+
// Return project directory path
|
|
56
|
+
return join(claudeConfigDir, 'projects', projectId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Helper to notify daemon about session (only once per session)
|
|
60
|
+
async function notifyDaemon(sessionId) {
|
|
61
|
+
if (daemonNotified || capturedSessionIds.has(sessionId)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
capturedSessionIds.add(sessionId);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const { readFileSync } = require('fs');
|
|
68
|
+
const { homedir } = require('os');
|
|
69
|
+
|
|
70
|
+
// Read daemon state to get HTTP port
|
|
71
|
+
const daemonStatePath = path.join(
|
|
72
|
+
process.env.ZENFLO_HOME_DIR || path.join(homedir(), '.happy'),
|
|
73
|
+
'daemon.state.json'
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
let daemonState;
|
|
77
|
+
try {
|
|
78
|
+
daemonState = JSON.parse(readFileSync(daemonStatePath, 'utf8'));
|
|
79
|
+
} catch (err) {
|
|
80
|
+
// Daemon not running or state file doesn't exist
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!daemonState.httpPort) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Notify daemon about the session
|
|
89
|
+
const metadata = {
|
|
90
|
+
path: process.cwd(),
|
|
91
|
+
host: require('os').hostname(),
|
|
92
|
+
hostPid: process.pid,
|
|
93
|
+
startedBy: 'claude-code-extension',
|
|
94
|
+
flavor: 'claude'
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const response = await fetch(`http://127.0.0.1:${daemonState.httpPort}/session-started`, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'Content-Type': 'application/json' },
|
|
100
|
+
body: JSON.stringify({ sessionId, metadata })
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (response.ok) {
|
|
104
|
+
daemonNotified = true;
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
// Ignore errors - daemon might not be ready or not running
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Helper to handle detected session
|
|
112
|
+
function handleSessionFile(sessionId) {
|
|
113
|
+
if (capturedSessionIds.has(sessionId)) {
|
|
114
|
+
return; // Already processed
|
|
115
|
+
}
|
|
116
|
+
capturedSessionIds.add(sessionId);
|
|
117
|
+
|
|
118
|
+
// Emit UUID message on fd 3 for session detection (when called from zenflo)
|
|
119
|
+
if (isCalledFromZenflo) {
|
|
120
|
+
writeMessage({ type: 'uuid', value: sessionId });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Notify daemon (when called directly)
|
|
124
|
+
if (!isCalledFromZenflo) {
|
|
125
|
+
notifyDaemon(sessionId).catch(() => {});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Helper to start session file watcher
|
|
130
|
+
function startSessionWatcher() {
|
|
131
|
+
const { watch, mkdirSync } = require('fs');
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const cwd = process.cwd();
|
|
135
|
+
const projectDir = getProjectPath(cwd);
|
|
136
|
+
mkdirSync(projectDir, { recursive: true});
|
|
137
|
+
|
|
138
|
+
debug('[launcher] Working directory:', cwd);
|
|
139
|
+
debug('[launcher] Watching for sessions in:', projectDir);
|
|
140
|
+
debug('[launcher] Called from zenflo:', isCalledFromZenflo);
|
|
141
|
+
|
|
142
|
+
// Watch for MODIFIED session files only (not existing ones)
|
|
143
|
+
// This ensures we only track sessions that are actively being used
|
|
144
|
+
sessionWatcher = watch(projectDir, (eventType, filename) => {
|
|
145
|
+
if (typeof filename === 'string' && filename.toLowerCase().endsWith('.jsonl')) {
|
|
146
|
+
const sessionId = filename.replace('.jsonl', '');
|
|
147
|
+
|
|
148
|
+
// Only handle 'change' events (file modifications), not 'rename' (file creation)
|
|
149
|
+
// This prevents tracking empty session files that are created but never used
|
|
150
|
+
if (eventType === 'change') {
|
|
151
|
+
debug('[launcher] Active session detected:', sessionId);
|
|
152
|
+
handleSessionFile(sessionId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
} catch (err) {
|
|
157
|
+
console.error('[launcher] Error starting session watcher:', err.message);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Start watching for session files
|
|
162
|
+
// - When called directly (extension): notify daemon
|
|
163
|
+
// - When called from zenflo with native binary: emit UUID messages for session detection
|
|
164
|
+
// - When called from zenflo with JS module: UUID messages come from crypto interception
|
|
165
|
+
startSessionWatcher();
|
|
166
|
+
|
|
16
167
|
// Intercept crypto.randomUUID
|
|
17
168
|
const originalRandomUUID = crypto.randomUUID;
|
|
18
169
|
Object.defineProperty(global, 'crypto', {
|
|
@@ -23,6 +174,7 @@ Object.defineProperty(global, 'crypto', {
|
|
|
23
174
|
randomUUID: () => {
|
|
24
175
|
const uuid = originalRandomUUID();
|
|
25
176
|
writeMessage({ type: 'uuid', value: uuid });
|
|
177
|
+
// Don't notify daemon from UUID interceptor - let file watcher handle it
|
|
26
178
|
return uuid;
|
|
27
179
|
}
|
|
28
180
|
};
|
|
@@ -35,6 +187,7 @@ Object.defineProperty(crypto, 'randomUUID', {
|
|
|
35
187
|
return () => {
|
|
36
188
|
const uuid = originalRandomUUID();
|
|
37
189
|
writeMessage({ type: 'uuid', value: uuid });
|
|
190
|
+
// Don't notify daemon from UUID interceptor - let file watcher handle it
|
|
38
191
|
return uuid;
|
|
39
192
|
}
|
|
40
193
|
}
|
|
@@ -51,15 +204,15 @@ global.fetch = function(...args) {
|
|
|
51
204
|
|
|
52
205
|
// Parse URL for privacy
|
|
53
206
|
let hostname = '';
|
|
54
|
-
let
|
|
207
|
+
let pathname = '';
|
|
55
208
|
try {
|
|
56
209
|
const urlObj = new URL(url, 'http://localhost');
|
|
57
210
|
hostname = urlObj.hostname;
|
|
58
|
-
|
|
211
|
+
pathname = urlObj.pathname;
|
|
59
212
|
} catch (e) {
|
|
60
213
|
// If URL parsing fails, use defaults
|
|
61
214
|
hostname = 'unknown';
|
|
62
|
-
|
|
215
|
+
pathname = url;
|
|
63
216
|
}
|
|
64
217
|
|
|
65
218
|
// Send fetch start event
|
|
@@ -67,7 +220,7 @@ global.fetch = function(...args) {
|
|
|
67
220
|
type: 'fetch-start',
|
|
68
221
|
id,
|
|
69
222
|
hostname,
|
|
70
|
-
path,
|
|
223
|
+
path: pathname,
|
|
71
224
|
method,
|
|
72
225
|
timestamp: Date.now()
|
|
73
226
|
});
|
|
@@ -95,4 +248,108 @@ global.fetch = function(...args) {
|
|
|
95
248
|
Object.defineProperty(global.fetch, 'name', { value: 'fetch' });
|
|
96
249
|
Object.defineProperty(global.fetch, 'length', { value: originalFetch.length });
|
|
97
250
|
|
|
98
|
-
|
|
251
|
+
// Get paths
|
|
252
|
+
const scriptDir = __dirname;
|
|
253
|
+
const cliDir = path.resolve(scriptDir, '..');
|
|
254
|
+
const zenfloBin = path.resolve(cliDir, 'bin', 'zenflo.mjs');
|
|
255
|
+
const claudeCodePath = path.resolve(cliDir, 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
|
|
256
|
+
const claudeCodePathMonorepo = path.resolve(cliDir, '..', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js');
|
|
257
|
+
|
|
258
|
+
if (!isCalledFromZenflo) {
|
|
259
|
+
// Called directly from Claude Code extension
|
|
260
|
+
// Just ensure daemon is running, then load Claude Code directly
|
|
261
|
+
// The file watcher will detect the session and notify daemon
|
|
262
|
+
try {
|
|
263
|
+
// Try to start zenflo daemon in background if not already running (non-blocking)
|
|
264
|
+
const daemonProcess = spawn(process.execPath, [zenfloBin, 'daemon', 'start'], {
|
|
265
|
+
detached: true,
|
|
266
|
+
stdio: 'ignore'
|
|
267
|
+
});
|
|
268
|
+
daemonProcess.unref();
|
|
269
|
+
// Give daemon a moment to start before we continue
|
|
270
|
+
setTimeout(() => {}, 500);
|
|
271
|
+
} catch (err) {
|
|
272
|
+
// Ignore errors - daemon might already be running
|
|
273
|
+
}
|
|
274
|
+
// Continue to load Claude Code directly - this creates ONE session
|
|
275
|
+
// The file watcher will detect it and notify daemon
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// We're being called from zenflo (or fallback) - just load Claude Code with intercepts
|
|
279
|
+
// Use require for CommonJS compatibility since this is a .cjs file
|
|
280
|
+
try {
|
|
281
|
+
// Check if native binary path was provided by extension wrapper
|
|
282
|
+
const nativeBinary = process.env.CLAUDE_CODE_NATIVE_BINARY;
|
|
283
|
+
|
|
284
|
+
// Debug logging to stderr (won't interfere with stdio)
|
|
285
|
+
if (nativeBinary) {
|
|
286
|
+
debug(`[launcher] Native binary path provided: ${nativeBinary}`);
|
|
287
|
+
debug(`[launcher] File exists: ${fs.existsSync(nativeBinary)}`);
|
|
288
|
+
} else {
|
|
289
|
+
debug('[launcher] No CLAUDE_CODE_NATIVE_BINARY env var');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (nativeBinary && fs.existsSync(nativeBinary)) {
|
|
293
|
+
debug('[launcher] Spawning native binary...');
|
|
294
|
+
debug('[launcher] Working directory:', process.cwd());
|
|
295
|
+
// Native binary is a compiled executable, need to spawn it instead of require
|
|
296
|
+
// Pass through all arguments, stdio, and working directory
|
|
297
|
+
const child = spawn(nativeBinary, process.argv.slice(2), {
|
|
298
|
+
stdio: 'inherit',
|
|
299
|
+
cwd: process.cwd(), // Ensure native binary uses correct working directory
|
|
300
|
+
env: process.env
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Forward all termination signals to child
|
|
304
|
+
const killChild = () => {
|
|
305
|
+
if (!child.killed) {
|
|
306
|
+
debug('[launcher] Killing child process...');
|
|
307
|
+
child.kill('SIGTERM');
|
|
308
|
+
// Force kill after 1 second if not dead
|
|
309
|
+
setTimeout(() => {
|
|
310
|
+
if (!child.killed) {
|
|
311
|
+
child.kill('SIGKILL');
|
|
312
|
+
}
|
|
313
|
+
}, 1000);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
process.on('SIGTERM', killChild);
|
|
318
|
+
process.on('SIGINT', killChild);
|
|
319
|
+
process.on('exit', killChild); // Also kill on normal exit (e.g., abort signal)
|
|
320
|
+
|
|
321
|
+
// Exit with child's exit code
|
|
322
|
+
child.on('exit', (code, signal) => {
|
|
323
|
+
if (signal) {
|
|
324
|
+
process.kill(process.pid, signal);
|
|
325
|
+
} else {
|
|
326
|
+
process.exit(code || 0);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
} else if (fs.existsSync(claudeCodePath)) {
|
|
330
|
+
debug('[launcher] Loading Claude Code from cli/node_modules');
|
|
331
|
+
require(claudeCodePath);
|
|
332
|
+
} else if (fs.existsSync(claudeCodePathMonorepo)) {
|
|
333
|
+
// Try monorepo root node_modules (Yarn workspace hoisting)
|
|
334
|
+
debug('[launcher] Loading Claude Code from monorepo node_modules');
|
|
335
|
+
require(claudeCodePathMonorepo);
|
|
336
|
+
} else {
|
|
337
|
+
// Fallback: try to use the system claude command
|
|
338
|
+
try {
|
|
339
|
+
const claudePath = execSync('which claude', { encoding: 'utf8' }).trim();
|
|
340
|
+
const resolvedPath = fs.realpathSync(claudePath);
|
|
341
|
+
require(resolvedPath);
|
|
342
|
+
} catch (whichErr) {
|
|
343
|
+
console.error('Failed to find Claude Code CLI');
|
|
344
|
+
console.error('Tried local path:', claudeCodePath);
|
|
345
|
+
console.error('Tried monorepo path:', claudeCodePathMonorepo);
|
|
346
|
+
console.error('Tried system claude command (not found)');
|
|
347
|
+
console.error('Make sure @anthropic-ai/claude-code is installed in node_modules or claude is in PATH');
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
} catch (err) {
|
|
352
|
+
console.error('Failed to load Claude Code CLI:', err.message);
|
|
353
|
+
console.error('Tried paths:', claudeCodePath, claudeCodePathMonorepo, process.env.CLAUDE_CODE_NATIVE_BINARY);
|
|
354
|
+
process.exit(1);
|
|
355
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Wrapper for Claude Code extension to launch ZenFlo instead of raw Claude
|
|
3
|
+
|
|
4
|
+
# The extension passes the native binary path as first argument
|
|
5
|
+
NATIVE_BINARY="$1"
|
|
6
|
+
shift
|
|
7
|
+
|
|
8
|
+
# Export the native binary path for the launcher to use
|
|
9
|
+
export CLAUDE_CODE_NATIVE_BINARY="$NATIVE_BINARY"
|
|
10
|
+
|
|
11
|
+
# Launch zenflo which creates full backend sessions
|
|
12
|
+
# ZenFlo will spawn Claude Code with proper stdio handling
|
|
13
|
+
exec zenflo "$@"
|