@siteboon/claude-code-ui 1.16.4 → 1.18.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.ko.md +369 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/dist/assets/index-BiDFBFLP.css +32 -0
- package/dist/assets/index-CF54Qj8d.js +1405 -0
- package/dist/assets/{vendor-codemirror-CJLzwpLB.js → vendor-codemirror-l-lAmaJ1.js} +21 -19
- package/dist/assets/{vendor-react-DcyRfQm3.js → vendor-react-DIN4KjD2.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +3 -2
- package/server/claude-sdk.js +48 -60
- package/server/database/auth.db +0 -0
- package/server/index.js +107 -78
- package/server/openai-codex.js +25 -12
- package/server/projects.js +117 -63
- package/server/routes/commands.js +80 -0
- package/server/routes/git.js +57 -17
- package/dist/assets/index-Cep8Annb.js +0 -1239
- package/dist/assets/index-DQad8ylc.css +0 -32
package/server/projects.js
CHANGED
|
@@ -384,6 +384,7 @@ async function getProjects(progressCallback = null) {
|
|
|
384
384
|
const config = await loadProjectConfig();
|
|
385
385
|
const projects = [];
|
|
386
386
|
const existingProjects = new Set();
|
|
387
|
+
const codexSessionsIndexRef = { sessionsByProject: null };
|
|
387
388
|
let totalProjects = 0;
|
|
388
389
|
let processedProjects = 0;
|
|
389
390
|
let directories = [];
|
|
@@ -419,8 +420,6 @@ async function getProjects(progressCallback = null) {
|
|
|
419
420
|
});
|
|
420
421
|
}
|
|
421
422
|
|
|
422
|
-
const projectPath = path.join(claudeDir, entry.name);
|
|
423
|
-
|
|
424
423
|
// Extract actual project directory from JSONL sessions
|
|
425
424
|
const actualProjectDir = await extractProjectDirectory(entry.name);
|
|
426
425
|
|
|
@@ -435,7 +434,11 @@ async function getProjects(progressCallback = null) {
|
|
|
435
434
|
displayName: customName || autoDisplayName,
|
|
436
435
|
fullPath: fullPath,
|
|
437
436
|
isCustomName: !!customName,
|
|
438
|
-
sessions: []
|
|
437
|
+
sessions: [],
|
|
438
|
+
sessionMeta: {
|
|
439
|
+
hasMore: false,
|
|
440
|
+
total: 0
|
|
441
|
+
}
|
|
439
442
|
};
|
|
440
443
|
|
|
441
444
|
// Try to get sessions for this project (just first 5 for performance)
|
|
@@ -448,6 +451,10 @@ async function getProjects(progressCallback = null) {
|
|
|
448
451
|
};
|
|
449
452
|
} catch (e) {
|
|
450
453
|
console.warn(`Could not load sessions for project ${entry.name}:`, e.message);
|
|
454
|
+
project.sessionMeta = {
|
|
455
|
+
hasMore: false,
|
|
456
|
+
total: 0
|
|
457
|
+
};
|
|
451
458
|
}
|
|
452
459
|
|
|
453
460
|
// Also fetch Cursor sessions for this project
|
|
@@ -460,7 +467,9 @@ async function getProjects(progressCallback = null) {
|
|
|
460
467
|
|
|
461
468
|
// Also fetch Codex sessions for this project
|
|
462
469
|
try {
|
|
463
|
-
project.codexSessions = await getCodexSessions(actualProjectDir
|
|
470
|
+
project.codexSessions = await getCodexSessions(actualProjectDir, {
|
|
471
|
+
indexRef: codexSessionsIndexRef,
|
|
472
|
+
});
|
|
464
473
|
} catch (e) {
|
|
465
474
|
console.warn(`Could not load Codex sessions for project ${entry.name}:`, e.message);
|
|
466
475
|
project.codexSessions = [];
|
|
@@ -525,7 +534,7 @@ async function getProjects(progressCallback = null) {
|
|
|
525
534
|
}
|
|
526
535
|
}
|
|
527
536
|
|
|
528
|
-
|
|
537
|
+
const project = {
|
|
529
538
|
name: projectName,
|
|
530
539
|
path: actualProjectDir,
|
|
531
540
|
displayName: projectConfig.displayName || await generateDisplayName(projectName, actualProjectDir),
|
|
@@ -533,9 +542,13 @@ async function getProjects(progressCallback = null) {
|
|
|
533
542
|
isCustomName: !!projectConfig.displayName,
|
|
534
543
|
isManuallyAdded: true,
|
|
535
544
|
sessions: [],
|
|
545
|
+
sessionMeta: {
|
|
546
|
+
hasMore: false,
|
|
547
|
+
total: 0
|
|
548
|
+
},
|
|
536
549
|
cursorSessions: [],
|
|
537
550
|
codexSessions: []
|
|
538
|
-
|
|
551
|
+
};
|
|
539
552
|
|
|
540
553
|
// Try to fetch Cursor sessions for manual projects too
|
|
541
554
|
try {
|
|
@@ -546,7 +559,9 @@ async function getProjects(progressCallback = null) {
|
|
|
546
559
|
|
|
547
560
|
// Try to fetch Codex sessions for manual projects too
|
|
548
561
|
try {
|
|
549
|
-
project.codexSessions = await getCodexSessions(actualProjectDir
|
|
562
|
+
project.codexSessions = await getCodexSessions(actualProjectDir, {
|
|
563
|
+
indexRef: codexSessionsIndexRef,
|
|
564
|
+
});
|
|
550
565
|
} catch (e) {
|
|
551
566
|
console.warn(`Could not load Codex sessions for manual project ${projectName}:`, e.message);
|
|
552
567
|
}
|
|
@@ -1244,75 +1259,114 @@ async function getCursorSessions(projectPath) {
|
|
|
1244
1259
|
}
|
|
1245
1260
|
|
|
1246
1261
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1262
|
+
function normalizeComparablePath(inputPath) {
|
|
1263
|
+
if (!inputPath || typeof inputPath !== 'string') {
|
|
1264
|
+
return '';
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const withoutLongPathPrefix = inputPath.startsWith('\\\\?\\')
|
|
1268
|
+
? inputPath.slice(4)
|
|
1269
|
+
: inputPath;
|
|
1270
|
+
const normalized = path.normalize(withoutLongPathPrefix.trim());
|
|
1271
|
+
|
|
1272
|
+
if (!normalized) {
|
|
1273
|
+
return '';
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
const resolved = path.resolve(normalized);
|
|
1277
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
async function findCodexJsonlFiles(dir) {
|
|
1281
|
+
const files = [];
|
|
1282
|
+
|
|
1250
1283
|
try {
|
|
1251
|
-
const
|
|
1252
|
-
const
|
|
1284
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1285
|
+
for (const entry of entries) {
|
|
1286
|
+
const fullPath = path.join(dir, entry.name);
|
|
1287
|
+
if (entry.isDirectory()) {
|
|
1288
|
+
files.push(...await findCodexJsonlFiles(fullPath));
|
|
1289
|
+
} else if (entry.name.endsWith('.jsonl')) {
|
|
1290
|
+
files.push(fullPath);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
} catch (error) {
|
|
1294
|
+
// Skip directories we can't read
|
|
1295
|
+
}
|
|
1253
1296
|
|
|
1254
|
-
|
|
1297
|
+
return files;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
async function buildCodexSessionsIndex() {
|
|
1301
|
+
const codexSessionsDir = path.join(os.homedir(), '.codex', 'sessions');
|
|
1302
|
+
const sessionsByProject = new Map();
|
|
1303
|
+
|
|
1304
|
+
try {
|
|
1305
|
+
await fs.access(codexSessionsDir);
|
|
1306
|
+
} catch (error) {
|
|
1307
|
+
return sessionsByProject;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const jsonlFiles = await findCodexJsonlFiles(codexSessionsDir);
|
|
1311
|
+
|
|
1312
|
+
for (const filePath of jsonlFiles) {
|
|
1255
1313
|
try {
|
|
1256
|
-
await
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
}
|
|
1314
|
+
const sessionData = await parseCodexSessionFile(filePath);
|
|
1315
|
+
if (!sessionData || !sessionData.id) {
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1261
1318
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
try {
|
|
1266
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
1267
|
-
for (const entry of entries) {
|
|
1268
|
-
const fullPath = path.join(dir, entry.name);
|
|
1269
|
-
if (entry.isDirectory()) {
|
|
1270
|
-
files.push(...await findJsonlFiles(fullPath));
|
|
1271
|
-
} else if (entry.name.endsWith('.jsonl')) {
|
|
1272
|
-
files.push(fullPath);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
} catch (error) {
|
|
1276
|
-
// Skip directories we can't read
|
|
1319
|
+
const normalizedProjectPath = normalizeComparablePath(sessionData.cwd);
|
|
1320
|
+
if (!normalizedProjectPath) {
|
|
1321
|
+
continue;
|
|
1277
1322
|
}
|
|
1278
|
-
return files;
|
|
1279
|
-
};
|
|
1280
1323
|
|
|
1281
|
-
|
|
1324
|
+
const session = {
|
|
1325
|
+
id: sessionData.id,
|
|
1326
|
+
summary: sessionData.summary || 'Codex Session',
|
|
1327
|
+
messageCount: sessionData.messageCount || 0,
|
|
1328
|
+
lastActivity: sessionData.timestamp ? new Date(sessionData.timestamp) : new Date(),
|
|
1329
|
+
cwd: sessionData.cwd,
|
|
1330
|
+
model: sessionData.model,
|
|
1331
|
+
filePath,
|
|
1332
|
+
provider: 'codex',
|
|
1333
|
+
};
|
|
1282
1334
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
try {
|
|
1286
|
-
const sessionData = await parseCodexSessionFile(filePath);
|
|
1287
|
-
|
|
1288
|
-
// Check if this session matches the project path
|
|
1289
|
-
// Handle Windows long paths with \\?\ prefix
|
|
1290
|
-
const sessionCwd = sessionData?.cwd || '';
|
|
1291
|
-
const cleanSessionCwd = sessionCwd.startsWith('\\\\?\\') ? sessionCwd.slice(4) : sessionCwd;
|
|
1292
|
-
const cleanProjectPath = projectPath.startsWith('\\\\?\\') ? projectPath.slice(4) : projectPath;
|
|
1293
|
-
|
|
1294
|
-
if (sessionData && (sessionData.cwd === projectPath || cleanSessionCwd === cleanProjectPath || path.relative(cleanSessionCwd, cleanProjectPath) === '')) {
|
|
1295
|
-
sessions.push({
|
|
1296
|
-
id: sessionData.id,
|
|
1297
|
-
summary: sessionData.summary || 'Codex Session',
|
|
1298
|
-
messageCount: sessionData.messageCount || 0,
|
|
1299
|
-
lastActivity: sessionData.timestamp ? new Date(sessionData.timestamp) : new Date(),
|
|
1300
|
-
cwd: sessionData.cwd,
|
|
1301
|
-
model: sessionData.model,
|
|
1302
|
-
filePath: filePath,
|
|
1303
|
-
provider: 'codex'
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
} catch (error) {
|
|
1307
|
-
console.warn(`Could not parse Codex session file ${filePath}:`, error.message);
|
|
1335
|
+
if (!sessionsByProject.has(normalizedProjectPath)) {
|
|
1336
|
+
sessionsByProject.set(normalizedProjectPath, []);
|
|
1308
1337
|
}
|
|
1338
|
+
|
|
1339
|
+
sessionsByProject.get(normalizedProjectPath).push(session);
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
console.warn(`Could not parse Codex session file ${filePath}:`, error.message);
|
|
1309
1342
|
}
|
|
1343
|
+
}
|
|
1310
1344
|
|
|
1311
|
-
|
|
1345
|
+
for (const sessions of sessionsByProject.values()) {
|
|
1312
1346
|
sessions.sort((a, b) => new Date(b.lastActivity) - new Date(a.lastActivity));
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
return sessionsByProject;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// Fetch Codex sessions for a given project path
|
|
1353
|
+
async function getCodexSessions(projectPath, options = {}) {
|
|
1354
|
+
const { limit = 5, indexRef = null } = options;
|
|
1355
|
+
try {
|
|
1356
|
+
const normalizedProjectPath = normalizeComparablePath(projectPath);
|
|
1357
|
+
if (!normalizedProjectPath) {
|
|
1358
|
+
return [];
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
if (indexRef && !indexRef.sessionsByProject) {
|
|
1362
|
+
indexRef.sessionsByProject = await buildCodexSessionsIndex();
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
const sessionsByProject = indexRef?.sessionsByProject || await buildCodexSessionsIndex();
|
|
1366
|
+
const sessions = sessionsByProject.get(normalizedProjectPath) || [];
|
|
1313
1367
|
|
|
1314
1368
|
// Return limited sessions for performance (0 = unlimited for deletion)
|
|
1315
|
-
return limit > 0 ? sessions.slice(0, limit) : sessions;
|
|
1369
|
+
return limit > 0 ? sessions.slice(0, limit) : [...sessions];
|
|
1316
1370
|
|
|
1317
1371
|
} catch (error) {
|
|
1318
1372
|
console.error('Error fetching Codex sessions:', error);
|
|
@@ -209,6 +209,86 @@ Custom commands can be created in:
|
|
|
209
209
|
};
|
|
210
210
|
},
|
|
211
211
|
|
|
212
|
+
'/cost': async (args, context) => {
|
|
213
|
+
const tokenUsage = context?.tokenUsage || {};
|
|
214
|
+
const provider = context?.provider || 'claude';
|
|
215
|
+
const model =
|
|
216
|
+
context?.model ||
|
|
217
|
+
(provider === 'cursor'
|
|
218
|
+
? CURSOR_MODELS.DEFAULT
|
|
219
|
+
: provider === 'codex'
|
|
220
|
+
? CODEX_MODELS.DEFAULT
|
|
221
|
+
: CLAUDE_MODELS.DEFAULT);
|
|
222
|
+
|
|
223
|
+
const used = Number(tokenUsage.used ?? tokenUsage.totalUsed ?? tokenUsage.total_tokens ?? 0) || 0;
|
|
224
|
+
const total =
|
|
225
|
+
Number(
|
|
226
|
+
tokenUsage.total ??
|
|
227
|
+
tokenUsage.contextWindow ??
|
|
228
|
+
parseInt(process.env.CONTEXT_WINDOW || '160000', 10),
|
|
229
|
+
) || 160000;
|
|
230
|
+
const percentage = total > 0 ? Number(((used / total) * 100).toFixed(1)) : 0;
|
|
231
|
+
|
|
232
|
+
const inputTokensRaw =
|
|
233
|
+
Number(
|
|
234
|
+
tokenUsage.inputTokens ??
|
|
235
|
+
tokenUsage.input ??
|
|
236
|
+
tokenUsage.cumulativeInputTokens ??
|
|
237
|
+
tokenUsage.promptTokens ??
|
|
238
|
+
0,
|
|
239
|
+
) || 0;
|
|
240
|
+
const outputTokens =
|
|
241
|
+
Number(
|
|
242
|
+
tokenUsage.outputTokens ??
|
|
243
|
+
tokenUsage.output ??
|
|
244
|
+
tokenUsage.cumulativeOutputTokens ??
|
|
245
|
+
tokenUsage.completionTokens ??
|
|
246
|
+
0,
|
|
247
|
+
) || 0;
|
|
248
|
+
const cacheTokens =
|
|
249
|
+
Number(
|
|
250
|
+
tokenUsage.cacheReadTokens ??
|
|
251
|
+
tokenUsage.cacheCreationTokens ??
|
|
252
|
+
tokenUsage.cacheTokens ??
|
|
253
|
+
tokenUsage.cachedTokens ??
|
|
254
|
+
0,
|
|
255
|
+
) || 0;
|
|
256
|
+
|
|
257
|
+
// If we only have total used tokens, treat them as input for display/estimation.
|
|
258
|
+
const inputTokens =
|
|
259
|
+
inputTokensRaw > 0 || outputTokens > 0 || cacheTokens > 0 ? inputTokensRaw + cacheTokens : used;
|
|
260
|
+
|
|
261
|
+
// Rough default rates by provider (USD / 1M tokens).
|
|
262
|
+
const pricingByProvider = {
|
|
263
|
+
claude: { input: 3, output: 15 },
|
|
264
|
+
cursor: { input: 3, output: 15 },
|
|
265
|
+
codex: { input: 1.5, output: 6 },
|
|
266
|
+
};
|
|
267
|
+
const rates = pricingByProvider[provider] || pricingByProvider.claude;
|
|
268
|
+
|
|
269
|
+
const inputCost = (inputTokens / 1_000_000) * rates.input;
|
|
270
|
+
const outputCost = (outputTokens / 1_000_000) * rates.output;
|
|
271
|
+
const totalCost = inputCost + outputCost;
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
type: 'builtin',
|
|
275
|
+
action: 'cost',
|
|
276
|
+
data: {
|
|
277
|
+
tokenUsage: {
|
|
278
|
+
used,
|
|
279
|
+
total,
|
|
280
|
+
percentage,
|
|
281
|
+
},
|
|
282
|
+
cost: {
|
|
283
|
+
input: inputCost.toFixed(4),
|
|
284
|
+
output: outputCost.toFixed(4),
|
|
285
|
+
total: totalCost.toFixed(4),
|
|
286
|
+
},
|
|
287
|
+
model,
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
},
|
|
291
|
+
|
|
212
292
|
'/status': async (args, context) => {
|
|
213
293
|
// Read version from package.json
|
|
214
294
|
const packageJsonPath = path.join(path.dirname(__dirname), '..', 'package.json');
|
package/server/routes/git.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
|
-
import { exec } from 'child_process';
|
|
2
|
+
import { exec, spawn } from 'child_process';
|
|
3
3
|
import { promisify } from 'util';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { promises as fs } from 'fs';
|
|
@@ -10,6 +10,43 @@ import { spawnCursor } from '../cursor-cli.js';
|
|
|
10
10
|
const router = express.Router();
|
|
11
11
|
const execAsync = promisify(exec);
|
|
12
12
|
|
|
13
|
+
function spawnAsync(command, args, options = {}) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const child = spawn(command, args, {
|
|
16
|
+
...options,
|
|
17
|
+
shell: false,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let stdout = '';
|
|
21
|
+
let stderr = '';
|
|
22
|
+
|
|
23
|
+
child.stdout.on('data', (data) => {
|
|
24
|
+
stdout += data.toString();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
child.stderr.on('data', (data) => {
|
|
28
|
+
stderr += data.toString();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
child.on('error', (error) => {
|
|
32
|
+
reject(error);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
child.on('close', (code) => {
|
|
36
|
+
if (code === 0) {
|
|
37
|
+
resolve({ stdout, stderr });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const error = new Error(`Command failed: ${command} ${args.join(' ')}`);
|
|
42
|
+
error.code = code;
|
|
43
|
+
error.stdout = stdout;
|
|
44
|
+
error.stderr = stderr;
|
|
45
|
+
reject(error);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
13
50
|
// Helper function to get the actual project path from the encoded project name
|
|
14
51
|
async function getActualProjectPath(projectName) {
|
|
15
52
|
try {
|
|
@@ -60,19 +97,16 @@ async function validateGitRepository(projectPath) {
|
|
|
60
97
|
}
|
|
61
98
|
|
|
62
99
|
try {
|
|
63
|
-
//
|
|
64
|
-
const { stdout:
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// Ensure the git root matches our project path (prevent using parent git repos)
|
|
69
|
-
if (normalizedGitRoot !== normalizedProjectPath) {
|
|
70
|
-
throw new Error(`Project directory is not a git repository. This directory is inside a git repository at ${normalizedGitRoot}, but git operations should be run from the repository root.`);
|
|
71
|
-
}
|
|
72
|
-
} catch (error) {
|
|
73
|
-
if (error.message.includes('Project directory is not a git repository')) {
|
|
74
|
-
throw error;
|
|
100
|
+
// Allow any directory that is inside a work tree (repo root or nested folder).
|
|
101
|
+
const { stdout: insideWorkTreeOutput } = await execAsync('git rev-parse --is-inside-work-tree', { cwd: projectPath });
|
|
102
|
+
const isInsideWorkTree = insideWorkTreeOutput.trim() === 'true';
|
|
103
|
+
if (!isInsideWorkTree) {
|
|
104
|
+
throw new Error('Not inside a git work tree');
|
|
75
105
|
}
|
|
106
|
+
|
|
107
|
+
// Ensure git can resolve the repository root for this directory.
|
|
108
|
+
await execAsync('git rev-parse --show-toplevel', { cwd: projectPath });
|
|
109
|
+
} catch {
|
|
76
110
|
throw new Error('Not a git repository. This directory does not contain a .git folder. Initialize a git repository with "git init" to use source control features.');
|
|
77
111
|
}
|
|
78
112
|
}
|
|
@@ -445,11 +479,17 @@ router.get('/commits', async (req, res) => {
|
|
|
445
479
|
|
|
446
480
|
try {
|
|
447
481
|
const projectPath = await getActualProjectPath(project);
|
|
482
|
+
await validateGitRepository(projectPath);
|
|
483
|
+
const parsedLimit = Number.parseInt(String(limit), 10);
|
|
484
|
+
const safeLimit = Number.isFinite(parsedLimit) && parsedLimit > 0
|
|
485
|
+
? Math.min(parsedLimit, 100)
|
|
486
|
+
: 10;
|
|
448
487
|
|
|
449
488
|
// Get commit log with stats
|
|
450
|
-
const { stdout } = await
|
|
451
|
-
|
|
452
|
-
|
|
489
|
+
const { stdout } = await spawnAsync(
|
|
490
|
+
'git',
|
|
491
|
+
['log', '--pretty=format:%H|%an|%ae|%ad|%s', '--date=relative', '-n', String(safeLimit)],
|
|
492
|
+
{ cwd: projectPath },
|
|
453
493
|
);
|
|
454
494
|
|
|
455
495
|
const commits = stdout
|
|
@@ -1125,4 +1165,4 @@ router.post('/delete-untracked', async (req, res) => {
|
|
|
1125
1165
|
}
|
|
1126
1166
|
});
|
|
1127
1167
|
|
|
1128
|
-
export default router;
|
|
1168
|
+
export default router;
|