@siteboon/claude-code-ui 1.18.2 → 1.19.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.
- package/README.ja.md +1 -25
- package/README.ko.md +1 -25
- package/README.md +1 -25
- package/README.zh-CN.md +1 -25
- package/dist/assets/index-BPHfv_yU.css +32 -0
- package/dist/assets/{index-Ukg3obwK.js → index-E8FwAGHq.js} +237 -235
- package/dist/index.html +2 -2
- package/package.json +5 -2
- package/scripts/fix-node-pty.js +67 -0
- package/server/claude-sdk.js +7 -1
- package/server/database/auth.db +0 -0
- package/server/index.js +5 -2
- package/server/projects.js +100 -12
- package/dist/assets/index-CrvzRggA.css +0 -32
package/dist/index.html
CHANGED
|
@@ -25,11 +25,11 @@
|
|
|
25
25
|
|
|
26
26
|
<!-- Prevent zoom on iOS -->
|
|
27
27
|
<meta name="format-detection" content="telephone=no" />
|
|
28
|
-
<script type="module" crossorigin src="/assets/index-
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-E8FwAGHq.js"></script>
|
|
29
29
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DIN4KjD2.js">
|
|
30
30
|
<link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-l-lAmaJ1.js">
|
|
31
31
|
<link rel="modulepreload" crossorigin href="/assets/vendor-xterm-DfaPXD3y.js">
|
|
32
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
32
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BPHfv_yU.css">
|
|
33
33
|
</head>
|
|
34
34
|
<body>
|
|
35
35
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@siteboon/claude-code-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.0",
|
|
4
4
|
"description": "A web-based UI for Claude Code CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server/index.js",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"server/",
|
|
13
13
|
"shared/",
|
|
14
14
|
"dist/",
|
|
15
|
+
"scripts/",
|
|
15
16
|
"README.md"
|
|
16
17
|
],
|
|
17
18
|
"homepage": "https://cloudcli.ai",
|
|
@@ -30,7 +31,8 @@
|
|
|
30
31
|
"preview": "vite preview",
|
|
31
32
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
32
33
|
"start": "npm run build && npm run server",
|
|
33
|
-
"release": "./release.sh"
|
|
34
|
+
"release": "./release.sh",
|
|
35
|
+
"postinstall": "node scripts/fix-node-pty.js"
|
|
34
36
|
},
|
|
35
37
|
"keywords": [
|
|
36
38
|
"claude code",
|
|
@@ -98,6 +100,7 @@
|
|
|
98
100
|
"ws": "^8.14.2"
|
|
99
101
|
},
|
|
100
102
|
"devDependencies": {
|
|
103
|
+
"@release-it/conventional-changelog": "^10.0.5",
|
|
101
104
|
"@types/node": "^22.19.7",
|
|
102
105
|
"@types/react": "^18.2.43",
|
|
103
106
|
"@types/react-dom": "^18.2.17",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fix node-pty spawn-helper permissions on macOS
|
|
4
|
+
*
|
|
5
|
+
* This script fixes a known issue with node-pty where the spawn-helper
|
|
6
|
+
* binary is shipped without execute permissions, causing "posix_spawnp failed" errors.
|
|
7
|
+
*
|
|
8
|
+
* @see https://github.com/microsoft/node-pty/issues/850
|
|
9
|
+
* @module scripts/fix-node-pty
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { promises as fs } from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fixes the spawn-helper binary permissions for node-pty on macOS.
|
|
21
|
+
*
|
|
22
|
+
* The node-pty package ships the spawn-helper binary without execute permissions
|
|
23
|
+
* (644 instead of 755), which causes "posix_spawnp failed" errors when trying
|
|
24
|
+
* to spawn terminal processes.
|
|
25
|
+
*
|
|
26
|
+
* This function:
|
|
27
|
+
* 1. Checks if running on macOS (darwin)
|
|
28
|
+
* 2. Locates spawn-helper binaries for both arm64 and x64 architectures
|
|
29
|
+
* 3. Sets execute permissions (755) on each binary found
|
|
30
|
+
*
|
|
31
|
+
* @async
|
|
32
|
+
* @function fixSpawnHelper
|
|
33
|
+
* @returns {Promise<void>} Resolves when permissions are fixed or skipped
|
|
34
|
+
* @example
|
|
35
|
+
* // Run as postinstall script
|
|
36
|
+
* await fixSpawnHelper();
|
|
37
|
+
*/
|
|
38
|
+
async function fixSpawnHelper() {
|
|
39
|
+
const nodeModulesPath = path.join(__dirname, '..', 'node_modules', 'node-pty', 'prebuilds');
|
|
40
|
+
|
|
41
|
+
// Only run on macOS
|
|
42
|
+
if (process.platform !== 'darwin') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const darwinDirs = ['darwin-arm64', 'darwin-x64'];
|
|
47
|
+
|
|
48
|
+
for (const dir of darwinDirs) {
|
|
49
|
+
const spawnHelperPath = path.join(nodeModulesPath, dir, 'spawn-helper');
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Check if file exists
|
|
53
|
+
await fs.access(spawnHelperPath);
|
|
54
|
+
|
|
55
|
+
// Make it executable (755)
|
|
56
|
+
await fs.chmod(spawnHelperPath, 0o755);
|
|
57
|
+
console.log(`[postinstall] Fixed permissions for ${spawnHelperPath}`);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
// File doesn't exist or other error - ignore
|
|
60
|
+
if (err.code !== 'ENOENT') {
|
|
61
|
+
console.warn(`[postinstall] Warning: Could not fix ${spawnHelperPath}: ${err.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fixSpawnHelper().catch(console.error);
|
package/server/claude-sdk.js
CHANGED
|
@@ -250,7 +250,13 @@ function getAllSessions() {
|
|
|
250
250
|
* @returns {Object} Transformed message ready for WebSocket
|
|
251
251
|
*/
|
|
252
252
|
function transformMessage(sdkMessage) {
|
|
253
|
-
//
|
|
253
|
+
// Extract parent_tool_use_id for subagent tool grouping
|
|
254
|
+
if (sdkMessage.parent_tool_use_id) {
|
|
255
|
+
return {
|
|
256
|
+
...sdkMessage,
|
|
257
|
+
parentToolUseId: sdkMessage.parent_tool_use_id
|
|
258
|
+
};
|
|
259
|
+
}
|
|
254
260
|
return sdkMessage;
|
|
255
261
|
}
|
|
256
262
|
|
|
Binary file
|
package/server/index.js
CHANGED
|
@@ -1886,6 +1886,9 @@ async function getFileTree(dirPath, maxDepth = 3, currentDepth = 0, showHidden =
|
|
|
1886
1886
|
}
|
|
1887
1887
|
|
|
1888
1888
|
const PORT = process.env.PORT || 3001;
|
|
1889
|
+
const HOST = process.env.HOST || '0.0.0.0';
|
|
1890
|
+
// Show localhost in URL when binding to all interfaces (0.0.0.0 isn't a connectable address)
|
|
1891
|
+
const DISPLAY_HOST = HOST === '0.0.0.0' ? 'localhost' : HOST;
|
|
1889
1892
|
|
|
1890
1893
|
// Initialize database and start server
|
|
1891
1894
|
async function startServer() {
|
|
@@ -1905,7 +1908,7 @@ async function startServer() {
|
|
|
1905
1908
|
console.log(`${c.warn('[WARN]')} Note: Requests will be proxied to Vite dev server at ${c.dim('http://localhost:' + (process.env.VITE_PORT || 5173))}`);
|
|
1906
1909
|
}
|
|
1907
1910
|
|
|
1908
|
-
server.listen(PORT,
|
|
1911
|
+
server.listen(PORT, HOST, async () => {
|
|
1909
1912
|
const appInstallPath = path.join(__dirname, '..');
|
|
1910
1913
|
|
|
1911
1914
|
console.log('');
|
|
@@ -1913,7 +1916,7 @@ async function startServer() {
|
|
|
1913
1916
|
console.log(` ${c.bright('Claude Code UI Server - Ready')}`);
|
|
1914
1917
|
console.log(c.dim('═'.repeat(63)));
|
|
1915
1918
|
console.log('');
|
|
1916
|
-
console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://
|
|
1919
|
+
console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://' + DISPLAY_HOST + ':' + PORT)}`);
|
|
1917
1920
|
console.log(`${c.info('[INFO]')} Installed at: ${c.dim(appInstallPath)}`);
|
|
1918
1921
|
console.log(`${c.tip('[TIP]')} Run "cloudcli status" for full configuration details`);
|
|
1919
1922
|
console.log('');
|
package/server/projects.js
CHANGED
|
@@ -889,22 +889,81 @@ async function parseJsonlSessions(filePath) {
|
|
|
889
889
|
}
|
|
890
890
|
}
|
|
891
891
|
|
|
892
|
+
// Parse an agent JSONL file and extract tool uses
|
|
893
|
+
async function parseAgentTools(filePath) {
|
|
894
|
+
const tools = [];
|
|
895
|
+
|
|
896
|
+
try {
|
|
897
|
+
const fileStream = fsSync.createReadStream(filePath);
|
|
898
|
+
const rl = readline.createInterface({
|
|
899
|
+
input: fileStream,
|
|
900
|
+
crlfDelay: Infinity
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
for await (const line of rl) {
|
|
904
|
+
if (line.trim()) {
|
|
905
|
+
try {
|
|
906
|
+
const entry = JSON.parse(line);
|
|
907
|
+
// Look for assistant messages with tool_use
|
|
908
|
+
if (entry.message?.role === 'assistant' && Array.isArray(entry.message?.content)) {
|
|
909
|
+
for (const part of entry.message.content) {
|
|
910
|
+
if (part.type === 'tool_use') {
|
|
911
|
+
tools.push({
|
|
912
|
+
toolId: part.id,
|
|
913
|
+
toolName: part.name,
|
|
914
|
+
toolInput: part.input,
|
|
915
|
+
timestamp: entry.timestamp
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
// Look for tool results
|
|
921
|
+
if (entry.message?.role === 'user' && Array.isArray(entry.message?.content)) {
|
|
922
|
+
for (const part of entry.message.content) {
|
|
923
|
+
if (part.type === 'tool_result') {
|
|
924
|
+
// Find the matching tool and add result
|
|
925
|
+
const tool = tools.find(t => t.toolId === part.tool_use_id);
|
|
926
|
+
if (tool) {
|
|
927
|
+
tool.toolResult = {
|
|
928
|
+
content: typeof part.content === 'string' ? part.content :
|
|
929
|
+
Array.isArray(part.content) ? part.content.map(c => c.text || '').join('\n') :
|
|
930
|
+
JSON.stringify(part.content),
|
|
931
|
+
isError: Boolean(part.is_error)
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
} catch (parseError) {
|
|
938
|
+
// Skip malformed lines
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
} catch (error) {
|
|
943
|
+
console.warn(`Error parsing agent file ${filePath}:`, error.message);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
return tools;
|
|
947
|
+
}
|
|
948
|
+
|
|
892
949
|
// Get messages for a specific session with pagination support
|
|
893
950
|
async function getSessionMessages(projectName, sessionId, limit = null, offset = 0) {
|
|
894
951
|
const projectDir = path.join(os.homedir(), '.claude', 'projects', projectName);
|
|
895
952
|
|
|
896
953
|
try {
|
|
897
954
|
const files = await fs.readdir(projectDir);
|
|
898
|
-
// agent-*.jsonl files contain
|
|
899
|
-
// periodically to make sure only accurate data is there and no new functionality is added there
|
|
955
|
+
// agent-*.jsonl files contain subagent tool history - we'll process them separately
|
|
900
956
|
const jsonlFiles = files.filter(file => file.endsWith('.jsonl') && !file.startsWith('agent-'));
|
|
901
|
-
|
|
957
|
+
const agentFiles = files.filter(file => file.endsWith('.jsonl') && file.startsWith('agent-'));
|
|
958
|
+
|
|
902
959
|
if (jsonlFiles.length === 0) {
|
|
903
960
|
return { messages: [], total: 0, hasMore: false };
|
|
904
961
|
}
|
|
905
|
-
|
|
962
|
+
|
|
906
963
|
const messages = [];
|
|
907
|
-
|
|
964
|
+
// Map of agentId -> tools for subagent tool grouping
|
|
965
|
+
const agentToolsCache = new Map();
|
|
966
|
+
|
|
908
967
|
// Process all JSONL files to find messages for this session
|
|
909
968
|
for (const file of jsonlFiles) {
|
|
910
969
|
const jsonlFile = path.join(projectDir, file);
|
|
@@ -913,7 +972,7 @@ async function getSessionMessages(projectName, sessionId, limit = null, offset =
|
|
|
913
972
|
input: fileStream,
|
|
914
973
|
crlfDelay: Infinity
|
|
915
974
|
});
|
|
916
|
-
|
|
975
|
+
|
|
917
976
|
for await (const line of rl) {
|
|
918
977
|
if (line.trim()) {
|
|
919
978
|
try {
|
|
@@ -927,26 +986,55 @@ async function getSessionMessages(projectName, sessionId, limit = null, offset =
|
|
|
927
986
|
}
|
|
928
987
|
}
|
|
929
988
|
}
|
|
930
|
-
|
|
989
|
+
|
|
990
|
+
// Collect agentIds from Task tool results
|
|
991
|
+
const agentIds = new Set();
|
|
992
|
+
for (const message of messages) {
|
|
993
|
+
if (message.toolUseResult?.agentId) {
|
|
994
|
+
agentIds.add(message.toolUseResult.agentId);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Load agent tools for each agentId found
|
|
999
|
+
for (const agentId of agentIds) {
|
|
1000
|
+
const agentFileName = `agent-${agentId}.jsonl`;
|
|
1001
|
+
if (agentFiles.includes(agentFileName)) {
|
|
1002
|
+
const agentFilePath = path.join(projectDir, agentFileName);
|
|
1003
|
+
const tools = await parseAgentTools(agentFilePath);
|
|
1004
|
+
agentToolsCache.set(agentId, tools);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Attach agent tools to their parent Task messages
|
|
1009
|
+
for (const message of messages) {
|
|
1010
|
+
if (message.toolUseResult?.agentId) {
|
|
1011
|
+
const agentId = message.toolUseResult.agentId;
|
|
1012
|
+
const agentTools = agentToolsCache.get(agentId);
|
|
1013
|
+
if (agentTools && agentTools.length > 0) {
|
|
1014
|
+
message.subagentTools = agentTools;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
931
1019
|
// Sort messages by timestamp
|
|
932
|
-
const sortedMessages = messages.sort((a, b) =>
|
|
1020
|
+
const sortedMessages = messages.sort((a, b) =>
|
|
933
1021
|
new Date(a.timestamp || 0) - new Date(b.timestamp || 0)
|
|
934
1022
|
);
|
|
935
|
-
|
|
1023
|
+
|
|
936
1024
|
const total = sortedMessages.length;
|
|
937
|
-
|
|
1025
|
+
|
|
938
1026
|
// If no limit is specified, return all messages (backward compatibility)
|
|
939
1027
|
if (limit === null) {
|
|
940
1028
|
return sortedMessages;
|
|
941
1029
|
}
|
|
942
|
-
|
|
1030
|
+
|
|
943
1031
|
// Apply pagination - for recent messages, we need to slice from the end
|
|
944
1032
|
// offset 0 should give us the most recent messages
|
|
945
1033
|
const startIndex = Math.max(0, total - offset - limit);
|
|
946
1034
|
const endIndex = total - offset;
|
|
947
1035
|
const paginatedMessages = sortedMessages.slice(startIndex, endIndex);
|
|
948
1036
|
const hasMore = startIndex > 0;
|
|
949
|
-
|
|
1037
|
+
|
|
950
1038
|
return {
|
|
951
1039
|
messages: paginatedMessages,
|
|
952
1040
|
total,
|