zenflo 0.11.7 → 0.11.9

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,92 @@
1
+ import { execSync } from 'child_process';
2
+ import { r as readDaemonState, l as logger } from './types-CTT5Uja5.mjs';
3
+ import chalk from 'chalk';
4
+ import 'axios';
5
+ import 'fs';
6
+ import 'node:fs';
7
+ import 'node:os';
8
+ import 'node:path';
9
+ import 'node:fs/promises';
10
+ import 'zod';
11
+ import 'node:crypto';
12
+ import 'tweetnacl';
13
+ import 'node:events';
14
+ import 'socket.io-client';
15
+ import 'util';
16
+ import 'fs/promises';
17
+ import 'crypto';
18
+ import 'path';
19
+ import 'url';
20
+ import 'os';
21
+ import 'expo-server-sdk';
22
+
23
+ const TASK_NAME = "ZenFlo Daemon";
24
+ async function status() {
25
+ try {
26
+ console.log(chalk.cyan.bold("\u{1F50D} ZenFlo Daemon Status\n"));
27
+ let isTaskInstalled = false;
28
+ let taskStatus = "Unknown";
29
+ try {
30
+ const output = execSync(`schtasks /Query /TN "${TASK_NAME}" /FO LIST /V`, { encoding: "utf-8" });
31
+ isTaskInstalled = true;
32
+ const statusMatch = output.match(/Status:\s+(.+)/);
33
+ if (statusMatch) {
34
+ taskStatus = statusMatch[1].trim();
35
+ }
36
+ } catch (error) {
37
+ isTaskInstalled = false;
38
+ }
39
+ const daemonState = await readDaemonState();
40
+ let mode;
41
+ if (isTaskInstalled) {
42
+ mode = "task";
43
+ } else if (daemonState?.pid) {
44
+ mode = "autostart";
45
+ } else {
46
+ mode = "none";
47
+ }
48
+ console.log(chalk.white.bold("Configuration:"));
49
+ if (mode === "task") {
50
+ console.log(chalk.green(" \u2713 Task Scheduler: Installed"));
51
+ console.log(chalk.dim(` Task Status: ${taskStatus}`));
52
+ console.log(chalk.dim(" Runs automatically at login"));
53
+ } else {
54
+ console.log(chalk.dim(" \u25CB Task Scheduler: Not installed"));
55
+ console.log(chalk.dim(" Using auto-start mode (starts with zenflo command)"));
56
+ }
57
+ console.log("");
58
+ console.log(chalk.white.bold("Daemon Status:"));
59
+ if (daemonState?.pid) {
60
+ console.log(chalk.green(" \u2713 Running"));
61
+ console.log(chalk.dim(` PID: ${daemonState.pid}`));
62
+ if (daemonState.httpPort) {
63
+ console.log(chalk.dim(` Port: ${daemonState.httpPort}`));
64
+ }
65
+ if (daemonState.startTime) {
66
+ const uptime = Math.floor((Date.now() - new Date(daemonState.startTime).getTime()) / 1e3 / 60);
67
+ console.log(chalk.dim(` Uptime: ${uptime} minutes`));
68
+ }
69
+ } else {
70
+ console.log(chalk.yellow(" \u25CB Not running"));
71
+ console.log(chalk.dim(" Start with: zenflo"));
72
+ }
73
+ console.log("");
74
+ console.log(chalk.white.bold("Available Commands:"));
75
+ if (mode === "task") {
76
+ console.log(chalk.dim(" \u2022 View in Task Scheduler: ") + chalk.white("taskschd.msc"));
77
+ console.log(chalk.dim(" \u2022 Stop task: ") + chalk.white(`schtasks /End /TN "${TASK_NAME}"`));
78
+ console.log(chalk.dim(" \u2022 Start task: ") + chalk.white(`schtasks /Run /TN "${TASK_NAME}"`));
79
+ console.log(chalk.dim(" \u2022 Uninstall: ") + chalk.white("zenflo daemon uninstall"));
80
+ } else {
81
+ console.log(chalk.dim(" \u2022 Install as task: ") + chalk.white("zenflo daemon install"));
82
+ console.log(chalk.dim(" \u2022 Start daemon: ") + chalk.white("zenflo"));
83
+ }
84
+ console.log("");
85
+ } catch (error) {
86
+ console.error(chalk.red("\u274C Status check failed:"), error instanceof Error ? error.message : error);
87
+ logger.debug("Failed to check daemon status:", error);
88
+ throw error;
89
+ }
90
+ }
91
+
92
+ export { status };
@@ -0,0 +1,99 @@
1
+ import { existsSync } from 'fs';
2
+ import { execSync } from 'child_process';
3
+ import { r as readDaemonState, l as logger } from './types-CTT5Uja5.mjs';
4
+ import os from 'os';
5
+ import path from 'path';
6
+ import chalk from 'chalk';
7
+ import 'axios';
8
+ import 'node:fs';
9
+ import 'node:os';
10
+ import 'node:path';
11
+ import 'node:fs/promises';
12
+ import 'zod';
13
+ import 'node:crypto';
14
+ import 'tweetnacl';
15
+ import 'node:events';
16
+ import 'socket.io-client';
17
+ import 'util';
18
+ import 'fs/promises';
19
+ import 'crypto';
20
+ import 'url';
21
+ import 'expo-server-sdk';
22
+
23
+ const SERVICE_NAME = "zenflo-daemon";
24
+ const SYSTEMD_USER_DIR = path.join(os.homedir(), ".config", "systemd", "user");
25
+ const SERVICE_FILE = path.join(SYSTEMD_USER_DIR, `${SERVICE_NAME}.service`);
26
+ async function status() {
27
+ try {
28
+ console.log(chalk.cyan.bold("\u{1F50D} ZenFlo Daemon Status\n"));
29
+ const isServiceInstalled = existsSync(SERVICE_FILE);
30
+ let serviceStatus = "Unknown";
31
+ let isServiceRunning = false;
32
+ if (isServiceInstalled) {
33
+ try {
34
+ const output = execSync(`systemctl --user is-active ${SERVICE_NAME}`, {
35
+ encoding: "utf-8",
36
+ stdio: "pipe"
37
+ }).trim();
38
+ serviceStatus = output;
39
+ isServiceRunning = output === "active";
40
+ } catch (error) {
41
+ serviceStatus = "inactive";
42
+ }
43
+ }
44
+ const daemonState = await readDaemonState();
45
+ let mode;
46
+ if (isServiceInstalled) {
47
+ mode = "systemd";
48
+ } else if (daemonState?.pid) {
49
+ mode = "autostart";
50
+ } else {
51
+ mode = "none";
52
+ }
53
+ console.log(chalk.white.bold("Configuration:"));
54
+ if (mode === "systemd") {
55
+ console.log(chalk.green(" \u2713 systemd Service: Installed"));
56
+ console.log(chalk.dim(` Service Status: ${serviceStatus}`));
57
+ console.log(chalk.dim(" Runs automatically at login"));
58
+ } else {
59
+ console.log(chalk.dim(" \u25CB systemd Service: Not installed"));
60
+ console.log(chalk.dim(" Using auto-start mode (starts with zenflo command)"));
61
+ }
62
+ console.log("");
63
+ console.log(chalk.white.bold("Daemon Status:"));
64
+ if (daemonState?.pid) {
65
+ console.log(chalk.green(" \u2713 Running"));
66
+ console.log(chalk.dim(` PID: ${daemonState.pid}`));
67
+ if (daemonState.httpPort) {
68
+ console.log(chalk.dim(` Port: ${daemonState.httpPort}`));
69
+ }
70
+ if (daemonState.startTime) {
71
+ const uptime = Math.floor((Date.now() - new Date(daemonState.startTime).getTime()) / 1e3 / 60);
72
+ console.log(chalk.dim(` Uptime: ${uptime} minutes`));
73
+ }
74
+ } else {
75
+ console.log(chalk.yellow(" \u25CB Not running"));
76
+ console.log(chalk.dim(" Start with: zenflo"));
77
+ }
78
+ console.log("");
79
+ console.log(chalk.white.bold("Available Commands:"));
80
+ if (mode === "systemd") {
81
+ console.log(chalk.dim(" \u2022 Check service: ") + chalk.white(`systemctl --user status ${SERVICE_NAME}`));
82
+ console.log(chalk.dim(" \u2022 View logs: ") + chalk.white(`journalctl --user -u ${SERVICE_NAME} -f`));
83
+ console.log(chalk.dim(" \u2022 Stop service: ") + chalk.white(`systemctl --user stop ${SERVICE_NAME}`));
84
+ console.log(chalk.dim(" \u2022 Start service: ") + chalk.white(`systemctl --user start ${SERVICE_NAME}`));
85
+ console.log(chalk.dim(" \u2022 Restart: ") + chalk.white(`systemctl --user restart ${SERVICE_NAME}`));
86
+ console.log(chalk.dim(" \u2022 Uninstall: ") + chalk.white("zenflo daemon uninstall"));
87
+ } else {
88
+ console.log(chalk.dim(" \u2022 Install as service: ") + chalk.white("zenflo daemon install"));
89
+ console.log(chalk.dim(" \u2022 Start daemon: ") + chalk.white("zenflo"));
90
+ }
91
+ console.log("");
92
+ } catch (error) {
93
+ console.error(chalk.red("\u274C Status check failed:"), error instanceof Error ? error.message : error);
94
+ logger.debug("Failed to check daemon status:", error);
95
+ throw error;
96
+ }
97
+ }
98
+
99
+ export { status };
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var child_process = require('child_process');
5
+ var os = require('os');
6
+ var path = require('path');
7
+ var chalk = require('chalk');
8
+ var types = require('./types-BNjAQcO4.cjs');
9
+ require('axios');
10
+ require('node:fs');
11
+ require('node:os');
12
+ require('node:path');
13
+ require('node:fs/promises');
14
+ require('zod');
15
+ require('node:crypto');
16
+ require('tweetnacl');
17
+ require('node:events');
18
+ require('socket.io-client');
19
+ require('util');
20
+ require('fs/promises');
21
+ require('crypto');
22
+ require('url');
23
+ require('expo-server-sdk');
24
+
25
+ const PLIST_LABEL = "com.zenflo.daemon";
26
+ const LAUNCH_AGENTS_DIR = path.join(os.homedir(), "Library", "LaunchAgents");
27
+ const PLIST_FILE = path.join(LAUNCH_AGENTS_DIR, `${PLIST_LABEL}.plist`);
28
+ async function status() {
29
+ console.log(chalk.cyan.bold("\u{1F4CA} ZenFlo Daemon Status\n"));
30
+ const isLaunchAgentInstalled = fs.existsSync(PLIST_FILE);
31
+ let isLaunchAgentLoaded = false;
32
+ if (isLaunchAgentInstalled) {
33
+ try {
34
+ const output = child_process.execSync("launchctl list", { encoding: "utf-8" });
35
+ isLaunchAgentLoaded = output.includes(PLIST_LABEL);
36
+ } catch (error) {
37
+ }
38
+ }
39
+ const daemonState = await types.readDaemonState();
40
+ const isDaemonRunning = daemonState !== null;
41
+ let mode;
42
+ if (isLaunchAgentInstalled && isLaunchAgentLoaded) {
43
+ mode = "launchagent";
44
+ } else if (isDaemonRunning) {
45
+ mode = "autostart";
46
+ } else {
47
+ mode = "none";
48
+ }
49
+ if (mode === "launchagent") {
50
+ console.log(chalk.green("\u2705 LaunchAgent Mode") + chalk.dim(" (installed & loaded)\n"));
51
+ console.log(chalk.cyan("Configuration:"));
52
+ console.log(chalk.dim(" \u2022 Mode: ") + chalk.white("LaunchAgent"));
53
+ console.log(chalk.dim(" \u2022 Location: ") + chalk.white(PLIST_FILE));
54
+ console.log(chalk.dim(" \u2022 Auto-start: ") + chalk.green("Yes") + chalk.dim(" (at login)"));
55
+ console.log(chalk.dim(" \u2022 System integration: ") + chalk.green("Enabled"));
56
+ if (isDaemonRunning && daemonState) {
57
+ console.log(chalk.dim(" \u2022 Status: ") + chalk.green("Running"));
58
+ console.log(chalk.dim(" \u2022 PID: ") + chalk.white(daemonState.pid.toString()));
59
+ console.log(chalk.dim(" \u2022 HTTP Port: ") + chalk.white(daemonState.httpPort.toString()));
60
+ console.log(chalk.dim(" \u2022 Started: ") + chalk.white(daemonState.startTime));
61
+ if (daemonState.lastHeartbeat) {
62
+ console.log(chalk.dim(" \u2022 Last heartbeat: ") + chalk.white(daemonState.lastHeartbeat));
63
+ }
64
+ } else {
65
+ console.log(chalk.dim(" \u2022 Status: ") + chalk.yellow("Not running"));
66
+ }
67
+ } else if (mode === "autostart") {
68
+ console.log(chalk.green("\u2705 Auto-Start Mode") + chalk.dim(" (default)\n"));
69
+ console.log(chalk.cyan("Configuration:"));
70
+ console.log(chalk.dim(" \u2022 Mode: ") + chalk.white("Auto-Start"));
71
+ console.log(chalk.dim(" \u2022 Auto-start: ") + chalk.green("Yes") + chalk.dim(" (when running zenflo)"));
72
+ console.log(chalk.dim(" \u2022 System integration: ") + chalk.yellow("Limited"));
73
+ if (daemonState) {
74
+ console.log(chalk.dim(" \u2022 Status: ") + chalk.green("Running"));
75
+ console.log(chalk.dim(" \u2022 PID: ") + chalk.white(daemonState.pid.toString()));
76
+ console.log(chalk.dim(" \u2022 HTTP Port: ") + chalk.white(daemonState.httpPort.toString()));
77
+ console.log(chalk.dim(" \u2022 Started: ") + chalk.white(daemonState.startTime));
78
+ if (daemonState.lastHeartbeat) {
79
+ console.log(chalk.dim(" \u2022 Last heartbeat: ") + chalk.white(daemonState.lastHeartbeat));
80
+ }
81
+ }
82
+ } else {
83
+ console.log(chalk.yellow("\u26A0\uFE0F Daemon Not Running\n"));
84
+ console.log(chalk.cyan("Configuration:"));
85
+ console.log(chalk.dim(" \u2022 LaunchAgent: ") + chalk.red("Not installed"));
86
+ console.log(chalk.dim(" \u2022 Daemon: ") + chalk.red("Not running"));
87
+ console.log(chalk.dim(" \u2022 Mode: ") + chalk.white("Auto-Start") + chalk.dim(" (will start when you run zenflo)"));
88
+ }
89
+ console.log("\n" + chalk.cyan("Available Commands:"));
90
+ if (mode === "launchagent") {
91
+ console.log(chalk.dim(" \u2022 Uninstall: ") + chalk.white("zenflo daemon uninstall"));
92
+ console.log(chalk.dim(" \u2022 Stop daemon: ") + chalk.white("zenflo daemon stop"));
93
+ console.log(chalk.dim(" \u2022 View logs: ") + chalk.white("tail -f ~/.zenflo/daemon.log"));
94
+ } else if (mode === "autostart") {
95
+ console.log(chalk.dim(" \u2022 Install LaunchAgent: ") + chalk.white("zenflo daemon install"));
96
+ console.log(chalk.dim(" \u2022 Stop daemon: ") + chalk.white("zenflo daemon stop"));
97
+ console.log(chalk.dim(" \u2022 View logs: ") + chalk.white("tail -f ~/.zenflo/daemon.log"));
98
+ } else {
99
+ console.log(chalk.dim(" \u2022 Install LaunchAgent: ") + chalk.white("zenflo daemon install"));
100
+ console.log(chalk.dim(" \u2022 Start daemon manually: ") + chalk.white("zenflo daemon start"));
101
+ console.log(chalk.dim(" \u2022 Run ZenFlo: ") + chalk.white("zenflo"));
102
+ }
103
+ console.log("");
104
+ }
105
+
106
+ exports.status = status;
@@ -42,7 +42,7 @@ function _interopNamespaceDefault(e) {
42
42
  var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
43
43
 
44
44
  var name = "zenflo";
45
- var version = "0.11.7";
45
+ var version = "0.11.9";
46
46
  var description = "Mobile and Web client for Claude Code and Codex - ZenFlo edition";
47
47
  var author = "Combined Memory";
48
48
  var license = "MIT";
@@ -1019,7 +1019,7 @@ class RpcHandlerManager {
1019
1019
  }
1020
1020
  }
1021
1021
 
1022
- const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-Dvhor4zW.cjs', document.baseURI).href))));
1022
+ const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-BNjAQcO4.cjs', document.baseURI).href))));
1023
1023
  function projectPath() {
1024
1024
  const path$1 = path.resolve(__dirname$1, "..");
1025
1025
  return path$1;
@@ -21,7 +21,7 @@ import { platform } from 'os';
21
21
  import { Expo } from 'expo-server-sdk';
22
22
 
23
23
  var name = "zenflo";
24
- var version = "0.11.7";
24
+ var version = "0.11.9";
25
25
  var description = "Mobile and Web client for Claude Code and Codex - ZenFlo edition";
26
26
  var author = "Combined Memory";
27
27
  var license = "MIT";
@@ -2182,4 +2182,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
2182
2182
  }).passthrough()
2183
2183
  ]);
2184
2184
 
2185
- export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, packageJson as b, configuration as c, backoff as d, delay as e, AsyncLock as f, readDaemonState as g, clearDaemonState as h, readCredentials as i, encodeBase64 as j, encodeBase64Url as k, logger as l, decodeBase64 as m, writeCredentialsDataKey as n, acquireDaemonLock as o, projectPath as p, writeDaemonState as q, readSettings as r, releaseDaemonLock as s, clearCredentials as t, updateSettings as u, clearMachineId as v, writeCredentialsLegacy as w, getLatestDaemonLog as x };
2185
+ export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, readSettings as b, configuration as c, packageJson as d, backoff as e, delay as f, AsyncLock as g, clearDaemonState as h, readCredentials as i, encodeBase64 as j, encodeBase64Url as k, logger as l, decodeBase64 as m, writeCredentialsDataKey as n, acquireDaemonLock as o, projectPath as p, writeDaemonState as q, readDaemonState as r, releaseDaemonLock as s, clearCredentials as t, updateSettings as u, clearMachineId as v, writeCredentialsLegacy as w, getLatestDaemonLog as x };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zenflo",
3
- "version": "0.11.7",
3
+ "version": "0.11.9",
4
4
  "description": "Mobile and Web client for Claude Code and Codex - ZenFlo edition",
5
5
  "author": "Combined Memory",
6
6
  "license": "MIT",
@@ -0,0 +1,309 @@
1
+ #!/usr/bin/env node
2
+
3
+ // CCR launcher for ZenFlo
4
+ // This script wraps ccr code to work within ZenFlo's session management
5
+
6
+ const crypto = require('crypto');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { execSync, spawn } = require('child_process');
10
+
11
+ // Disable autoupdater
12
+ process.env.DISABLE_AUTOUPDATER = '1';
13
+
14
+ // Debug helper
15
+ const DEBUG = process.env.DEBUG === '1' || process.env.DEBUG === 'true';
16
+ function debug(...args) {
17
+ if (DEBUG) {
18
+ console.error(...args);
19
+ }
20
+ }
21
+
22
+ // Helper to write JSON messages to fd 3
23
+ function writeMessage(message) {
24
+ try {
25
+ fs.writeSync(3, JSON.stringify(message) + '\n');
26
+ } catch (err) {
27
+ // fd 3 not available, ignore
28
+ }
29
+ }
30
+
31
+ // Check if being called from zenflo
32
+ let isCalledFromZenflo = false;
33
+ try {
34
+ fs.writeSync(3, '');
35
+ isCalledFromZenflo = true;
36
+ } catch (err) {
37
+ isCalledFromZenflo = false;
38
+ }
39
+
40
+ // Track session IDs
41
+ const capturedSessionIds = new Set();
42
+ let daemonNotified = false;
43
+ let sessionWatcher = null;
44
+
45
+ // Helper to get project path
46
+ function getProjectPath(workingDirectory) {
47
+ const { join, resolve } = require('path');
48
+ const { homedir } = require('os');
49
+
50
+ const projectId = resolve(workingDirectory).replace(/[\\\/\.:]/g, '-');
51
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
52
+
53
+ return join(claudeConfigDir, 'projects', projectId);
54
+ }
55
+
56
+ // Helper to notify daemon about session
57
+ async function notifyDaemon(sessionId) {
58
+ if (daemonNotified || capturedSessionIds.has(sessionId)) {
59
+ return;
60
+ }
61
+ capturedSessionIds.add(sessionId);
62
+
63
+ try {
64
+ const { readFileSync } = require('fs');
65
+ const { homedir } = require('os');
66
+
67
+ const daemonStatePath = path.join(
68
+ process.env.ZENFLO_HOME_DIR || path.join(homedir(), '.happy'),
69
+ 'daemon.state.json'
70
+ );
71
+
72
+ let daemonState;
73
+ try {
74
+ daemonState = JSON.parse(readFileSync(daemonStatePath, 'utf8'));
75
+ } catch (err) {
76
+ return;
77
+ }
78
+
79
+ if (!daemonState.httpPort) {
80
+ return;
81
+ }
82
+
83
+ const metadata = {
84
+ path: process.cwd(),
85
+ host: require('os').hostname(),
86
+ hostPid: process.pid,
87
+ startedBy: 'zenflo-cli',
88
+ flavor: 'ccr' // Mark this session as CCR flavor
89
+ };
90
+
91
+ const response = await fetch(`http://127.0.0.1:${daemonState.httpPort}/session-started`, {
92
+ method: 'POST',
93
+ headers: { 'Content-Type': 'application/json' },
94
+ body: JSON.stringify({ sessionId, metadata })
95
+ });
96
+
97
+ if (response.ok) {
98
+ daemonNotified = true;
99
+ }
100
+ } catch (err) {
101
+ // Ignore errors
102
+ }
103
+ }
104
+
105
+ // Handle detected session
106
+ function handleSessionFile(sessionId) {
107
+ if (capturedSessionIds.has(sessionId)) {
108
+ return;
109
+ }
110
+ capturedSessionIds.add(sessionId);
111
+
112
+ if (isCalledFromZenflo) {
113
+ writeMessage({ type: 'uuid', value: sessionId });
114
+ }
115
+
116
+ if (!isCalledFromZenflo) {
117
+ notifyDaemon(sessionId).catch(() => {});
118
+ }
119
+ }
120
+
121
+ // Start session file watcher
122
+ function startSessionWatcher() {
123
+ const { watch, mkdirSync } = require('fs');
124
+
125
+ try {
126
+ const cwd = process.cwd();
127
+ const projectDir = getProjectPath(cwd);
128
+ mkdirSync(projectDir, { recursive: true});
129
+
130
+ debug('[ccr-launcher] Working directory:', cwd);
131
+ debug('[ccr-launcher] Watching for sessions in:', projectDir);
132
+ debug('[ccr-launcher] Called from zenflo:', isCalledFromZenflo);
133
+
134
+ sessionWatcher = watch(projectDir, (eventType, filename) => {
135
+ if (typeof filename === 'string' && filename.toLowerCase().endsWith('.jsonl')) {
136
+ const sessionId = filename.replace('.jsonl', '');
137
+
138
+ if (eventType === 'change') {
139
+ debug('[ccr-launcher] Active session detected:', sessionId);
140
+ handleSessionFile(sessionId);
141
+ }
142
+ }
143
+ });
144
+ } catch (err) {
145
+ console.error('[ccr-launcher] Error starting session watcher:', err.message);
146
+ }
147
+ }
148
+
149
+ // Start watching
150
+ startSessionWatcher();
151
+
152
+ // Intercept crypto.randomUUID
153
+ const originalRandomUUID = crypto.randomUUID;
154
+ Object.defineProperty(global, 'crypto', {
155
+ configurable: true,
156
+ enumerable: true,
157
+ get() {
158
+ return {
159
+ randomUUID: () => {
160
+ const uuid = originalRandomUUID();
161
+ writeMessage({ type: 'uuid', value: uuid });
162
+ return uuid;
163
+ }
164
+ };
165
+ }
166
+ });
167
+ Object.defineProperty(crypto, 'randomUUID', {
168
+ configurable: true,
169
+ enumerable: true,
170
+ get() {
171
+ return () => {
172
+ const uuid = originalRandomUUID();
173
+ writeMessage({ type: 'uuid', value: uuid });
174
+ return uuid;
175
+ }
176
+ }
177
+ });
178
+
179
+ // Intercept fetch to track activity
180
+ const originalFetch = global.fetch;
181
+ let fetchCounter = 0;
182
+
183
+ global.fetch = function(...args) {
184
+ const id = ++fetchCounter;
185
+ const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || '';
186
+ const method = args[1]?.method || 'GET';
187
+
188
+ let hostname = '';
189
+ let pathname = '';
190
+ try {
191
+ const urlObj = new URL(url, 'http://localhost');
192
+ hostname = urlObj.hostname;
193
+ pathname = urlObj.pathname;
194
+ } catch (e) {
195
+ hostname = 'unknown';
196
+ pathname = url;
197
+ }
198
+
199
+ writeMessage({
200
+ type: 'fetch-start',
201
+ id,
202
+ hostname,
203
+ path: pathname,
204
+ method,
205
+ timestamp: Date.now()
206
+ });
207
+
208
+ const fetchPromise = originalFetch(...args);
209
+
210
+ const sendEnd = () => {
211
+ writeMessage({
212
+ type: 'fetch-end',
213
+ id,
214
+ timestamp: Date.now()
215
+ });
216
+ };
217
+
218
+ fetchPromise.then(sendEnd, sendEnd);
219
+
220
+ return fetchPromise;
221
+ };
222
+
223
+ Object.defineProperty(global.fetch, 'name', { value: 'fetch' });
224
+ Object.defineProperty(global.fetch, 'length', { value: originalFetch.length });
225
+
226
+ if (!isCalledFromZenflo) {
227
+ // Start zenflo daemon in background
228
+ const scriptDir = __dirname;
229
+ const cliDir = path.resolve(scriptDir, '..');
230
+ const zenfloBin = path.resolve(cliDir, 'bin', 'zenflo.mjs');
231
+
232
+ try {
233
+ const daemonProcess = spawn(process.execPath, [zenfloBin, 'daemon', 'start'], {
234
+ detached: true,
235
+ stdio: 'ignore'
236
+ });
237
+ daemonProcess.unref();
238
+ setTimeout(() => {}, 500);
239
+ } catch (err) {
240
+ // Ignore errors
241
+ }
242
+ }
243
+
244
+ // Check if CCR is available
245
+ try {
246
+ execSync('which ccr', { encoding: 'utf8', stdio: 'pipe' });
247
+ } catch (err) {
248
+ console.error('❌ Claude Code Router (ccr) not found!');
249
+ console.error('');
250
+ console.error('Please install CCR first:');
251
+ console.error(' npm install -g @musistudio/claude-code-router');
252
+ console.error('');
253
+ console.error('Or use the local installation at:');
254
+ console.error(' /Users/quinnmay/claude-code-router/ccr-claude');
255
+ process.exit(1);
256
+ }
257
+
258
+ // Check if CCR is running
259
+ try {
260
+ const status = execSync('ccr status', { encoding: 'utf8', stdio: 'pipe' });
261
+ if (!status.includes('Running')) {
262
+ debug('[ccr-launcher] Starting CCR service...');
263
+ execSync('ccr start', { stdio: 'ignore' });
264
+ // Give it a moment to start
265
+ const start = Date.now();
266
+ while (Date.now() - start < 5000) {
267
+ try {
268
+ const check = execSync('ccr status', { encoding: 'utf8', stdio: 'pipe' });
269
+ if (check.includes('Running')) {
270
+ break;
271
+ }
272
+ } catch (e) {
273
+ // Keep waiting
274
+ }
275
+ // Sleep for 100ms
276
+ execSync('sleep 0.1', { stdio: 'ignore' });
277
+ }
278
+ }
279
+ } catch (err) {
280
+ console.error('[ccr-launcher] Failed to start CCR:', err.message);
281
+ process.exit(1);
282
+ }
283
+
284
+ // Set environment to route through CCR
285
+ process.env.ANTHROPIC_BASE_URL = 'http://localhost:3456';
286
+ process.env.ANTHROPIC_API_KEY = 'ccr-proxy-key';
287
+
288
+ debug('[ccr-launcher] CCR is running, launching Claude Code...');
289
+ debug('[ccr-launcher] ANTHROPIC_BASE_URL:', process.env.ANTHROPIC_BASE_URL);
290
+
291
+ // Find Claude Code binary
292
+ let claudeCodePath;
293
+ try {
294
+ const claudePath = execSync('which claude', { encoding: 'utf8', stdio: 'pipe' }).trim();
295
+ claudeCodePath = fs.realpathSync(claudePath);
296
+ } catch (err) {
297
+ console.error('Failed to find Claude Code CLI');
298
+ console.error('Make sure claude is installed: npm install -g @anthropic-ai/claude-code');
299
+ process.exit(1);
300
+ }
301
+
302
+ // Load Claude Code
303
+ try {
304
+ debug('[ccr-launcher] Loading Claude Code from:', claudeCodePath);
305
+ require(claudeCodePath);
306
+ } catch (err) {
307
+ console.error('Failed to load Claude Code CLI:', err.message);
308
+ process.exit(1);
309
+ }
@@ -17,17 +17,18 @@ const os = require('os');
17
17
  function getPlatformDir() {
18
18
  const platform = os.platform();
19
19
  const arch = os.arch();
20
-
20
+
21
21
  if (platform === 'darwin') {
22
22
  if (arch === 'arm64') return 'arm64-darwin';
23
23
  if (arch === 'x64') return 'x64-darwin';
24
- } else if (platform === 'linux') {
24
+ } else if (platform === 'linux' || platform === 'android') {
25
+ // Android uses Linux binaries
25
26
  if (arch === 'arm64') return 'arm64-linux';
26
27
  if (arch === 'x64') return 'x64-linux';
27
28
  } else if (platform === 'win32') {
28
29
  if (arch === 'x64') return 'x64-win32';
29
30
  }
30
-
31
+
31
32
  throw new Error(`Unsupported platform: ${arch}-${platform}`);
32
33
  }
33
34