zenflo 0.11.8 → 0.11.10
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/{index-BSKmq2tY.mjs → index-BW7_WZAp.mjs} +554 -79
- package/dist/{index-DXM6ppoL.cjs → index-CIEIMabk.cjs} +552 -76
- package/dist/index.cjs +3 -3
- package/dist/index.mjs +3 -3
- package/dist/lib.cjs +1 -1
- package/dist/lib.mjs +1 -1
- package/dist/runCCR-COQ_d0t_.cjs +103 -0
- package/dist/runCCR-CQVvjom9.mjs +101 -0
- package/dist/{runCodex-C5pAgp-V.cjs → runCodex-CWrjFTDA.cjs} +2 -2
- package/dist/{runCodex-DoQNB2J-.mjs → runCodex-zJRwP8WW.mjs} +2 -2
- package/dist/status-4hHpWyGc.mjs +104 -0
- package/dist/status-CXWZdbyc.cjs +101 -0
- package/dist/status-DChMScEv.cjs +106 -0
- package/dist/status-DT-lQ5z3.mjs +99 -0
- package/dist/status-EIRGrJzv.mjs +92 -0
- package/dist/status-a3Xws-4m.cjs +94 -0
- package/dist/{types-uaIx8ab0.cjs → types-CVUeTsiC.cjs} +2 -2
- package/dist/{types-qQzfAnur.mjs → types-Kg2WtK0J.mjs} +2 -2
- package/package.json +1 -1
- package/scripts/ccr_launcher.cjs +309 -0
|
@@ -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-Kg2WtK0J.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,92 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { r as readDaemonState, l as logger } from './types-Kg2WtK0J.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,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var child_process = require('child_process');
|
|
4
|
+
var types = require('./types-CVUeTsiC.cjs');
|
|
5
|
+
var chalk = require('chalk');
|
|
6
|
+
require('axios');
|
|
7
|
+
require('fs');
|
|
8
|
+
require('node:fs');
|
|
9
|
+
require('node:os');
|
|
10
|
+
require('node:path');
|
|
11
|
+
require('node:fs/promises');
|
|
12
|
+
require('zod');
|
|
13
|
+
require('node:crypto');
|
|
14
|
+
require('tweetnacl');
|
|
15
|
+
require('node:events');
|
|
16
|
+
require('socket.io-client');
|
|
17
|
+
require('util');
|
|
18
|
+
require('fs/promises');
|
|
19
|
+
require('crypto');
|
|
20
|
+
require('path');
|
|
21
|
+
require('url');
|
|
22
|
+
require('os');
|
|
23
|
+
require('expo-server-sdk');
|
|
24
|
+
|
|
25
|
+
const TASK_NAME = "ZenFlo Daemon";
|
|
26
|
+
async function status() {
|
|
27
|
+
try {
|
|
28
|
+
console.log(chalk.cyan.bold("\u{1F50D} ZenFlo Daemon Status\n"));
|
|
29
|
+
let isTaskInstalled = false;
|
|
30
|
+
let taskStatus = "Unknown";
|
|
31
|
+
try {
|
|
32
|
+
const output = child_process.execSync(`schtasks /Query /TN "${TASK_NAME}" /FO LIST /V`, { encoding: "utf-8" });
|
|
33
|
+
isTaskInstalled = true;
|
|
34
|
+
const statusMatch = output.match(/Status:\s+(.+)/);
|
|
35
|
+
if (statusMatch) {
|
|
36
|
+
taskStatus = statusMatch[1].trim();
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
isTaskInstalled = false;
|
|
40
|
+
}
|
|
41
|
+
const daemonState = await types.readDaemonState();
|
|
42
|
+
let mode;
|
|
43
|
+
if (isTaskInstalled) {
|
|
44
|
+
mode = "task";
|
|
45
|
+
} else if (daemonState?.pid) {
|
|
46
|
+
mode = "autostart";
|
|
47
|
+
} else {
|
|
48
|
+
mode = "none";
|
|
49
|
+
}
|
|
50
|
+
console.log(chalk.white.bold("Configuration:"));
|
|
51
|
+
if (mode === "task") {
|
|
52
|
+
console.log(chalk.green(" \u2713 Task Scheduler: Installed"));
|
|
53
|
+
console.log(chalk.dim(` Task Status: ${taskStatus}`));
|
|
54
|
+
console.log(chalk.dim(" Runs automatically at login"));
|
|
55
|
+
} else {
|
|
56
|
+
console.log(chalk.dim(" \u25CB Task Scheduler: Not installed"));
|
|
57
|
+
console.log(chalk.dim(" Using auto-start mode (starts with zenflo command)"));
|
|
58
|
+
}
|
|
59
|
+
console.log("");
|
|
60
|
+
console.log(chalk.white.bold("Daemon Status:"));
|
|
61
|
+
if (daemonState?.pid) {
|
|
62
|
+
console.log(chalk.green(" \u2713 Running"));
|
|
63
|
+
console.log(chalk.dim(` PID: ${daemonState.pid}`));
|
|
64
|
+
if (daemonState.httpPort) {
|
|
65
|
+
console.log(chalk.dim(` Port: ${daemonState.httpPort}`));
|
|
66
|
+
}
|
|
67
|
+
if (daemonState.startTime) {
|
|
68
|
+
const uptime = Math.floor((Date.now() - new Date(daemonState.startTime).getTime()) / 1e3 / 60);
|
|
69
|
+
console.log(chalk.dim(` Uptime: ${uptime} minutes`));
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
console.log(chalk.yellow(" \u25CB Not running"));
|
|
73
|
+
console.log(chalk.dim(" Start with: zenflo"));
|
|
74
|
+
}
|
|
75
|
+
console.log("");
|
|
76
|
+
console.log(chalk.white.bold("Available Commands:"));
|
|
77
|
+
if (mode === "task") {
|
|
78
|
+
console.log(chalk.dim(" \u2022 View in Task Scheduler: ") + chalk.white("taskschd.msc"));
|
|
79
|
+
console.log(chalk.dim(" \u2022 Stop task: ") + chalk.white(`schtasks /End /TN "${TASK_NAME}"`));
|
|
80
|
+
console.log(chalk.dim(" \u2022 Start task: ") + chalk.white(`schtasks /Run /TN "${TASK_NAME}"`));
|
|
81
|
+
console.log(chalk.dim(" \u2022 Uninstall: ") + chalk.white("zenflo daemon uninstall"));
|
|
82
|
+
} else {
|
|
83
|
+
console.log(chalk.dim(" \u2022 Install as task: ") + chalk.white("zenflo daemon install"));
|
|
84
|
+
console.log(chalk.dim(" \u2022 Start daemon: ") + chalk.white("zenflo"));
|
|
85
|
+
}
|
|
86
|
+
console.log("");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(chalk.red("\u274C Status check failed:"), error instanceof Error ? error.message : error);
|
|
89
|
+
types.logger.debug("Failed to check daemon status:", error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
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.
|
|
45
|
+
var version = "0.11.10";
|
|
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-
|
|
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-CVUeTsiC.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.
|
|
24
|
+
var version = "0.11.10";
|
|
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,
|
|
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
|
@@ -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
|
+
}
|