codexmate 0.0.8 → 0.0.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/.planning/.fix-attempts +1 -0
- package/.planning/.lock +6 -0
- package/.planning/.verify-cache.json +14 -0
- package/.planning/CHECKPOINT.json +46 -0
- package/.planning/DESIGN.md +26 -0
- package/.planning/HISTORY.json +124 -0
- package/.planning/PLAN.md +69 -0
- package/.planning/REVIEW.md +41 -0
- package/.planning/STATE.md +12 -0
- package/.planning/STATS.json +13 -0
- package/.planning/VERIFICATION.md +70 -0
- package/.planning/daude-code-plan.md +51 -0
- package/.planning/research/architecture.md +32 -0
- package/.planning/research/conventions.md +36 -0
- package/.planning/task_1-REVIEW.md +29 -0
- package/.planning/task_1-SUMMARY.md +32 -0
- package/.planning/task_2-REVIEW.md +24 -0
- package/.planning/task_2-SUMMARY.md +37 -0
- package/.planning/task_3-REVIEW.md +25 -0
- package/.planning/task_3-SUMMARY.md +31 -0
- package/README.md +11 -12
- package/README.zh-CN.md +25 -20
- package/cli.js +291 -156
- package/lib/cli-file-utils.js +9 -7
- package/package.json +3 -2
- package/res/json5.min.js +1 -0
- package/res/vue.global.js +18552 -0
- package/tests/e2e/run.js +19 -4
- package/tests/e2e/test-health-speed.js +5 -1
- package/tests/e2e/test-session-search.js +114 -0
- package/tests/e2e/test-sessions.js +22 -13
- package/tests/e2e/test-setup.js +83 -14
- package/tests/unit/run.mjs +29 -0
- package/tests/unit/web-ui-logic.test.mjs +186 -0
- package/web-ui/app.js +2841 -0
- package/web-ui/logic.mjs +157 -0
- package/web-ui.html +574 -3014
package/cli.js
CHANGED
|
@@ -85,9 +85,9 @@ const MAX_SESSION_DETAIL_MESSAGES = 1000;
|
|
|
85
85
|
const SESSION_TITLE_READ_BYTES = 64 * 1024;
|
|
86
86
|
const CODEXMATE_MANAGED_MARKER = '# codexmate-managed: true';
|
|
87
87
|
const SESSION_LIST_CACHE_TTL_MS = 4000;
|
|
88
|
-
const SESSION_SUMMARY_READ_BYTES = 256 * 1024;
|
|
89
|
-
const SESSION_CONTENT_READ_BYTES = SESSION_SUMMARY_READ_BYTES;
|
|
90
|
-
const DEFAULT_CONTENT_SCAN_LIMIT =
|
|
88
|
+
const SESSION_SUMMARY_READ_BYTES = 256 * 1024;
|
|
89
|
+
const SESSION_CONTENT_READ_BYTES = SESSION_SUMMARY_READ_BYTES;
|
|
90
|
+
const DEFAULT_CONTENT_SCAN_LIMIT = 50;
|
|
91
91
|
const SESSION_SCAN_FACTOR = 4;
|
|
92
92
|
const SESSION_SCAN_MIN_FILES = 800;
|
|
93
93
|
const MAX_SESSION_PATH_LIST_SIZE = 2000;
|
|
@@ -1530,17 +1530,17 @@ function isBootstrapLikeText(text) {
|
|
|
1530
1530
|
return BOOTSTRAP_TEXT_MARKERS.some(marker => normalized.includes(marker));
|
|
1531
1531
|
}
|
|
1532
1532
|
|
|
1533
|
-
function removeLeadingSystemMessage(messages) {
|
|
1534
|
-
if (!Array.isArray(messages) || messages.length === 0) {
|
|
1535
|
-
return [];
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
let startIndex =
|
|
1539
|
-
while (startIndex < messages.length) {
|
|
1540
|
-
const item = messages[startIndex];
|
|
1541
|
-
const role = item ? normalizeRole(item.role) : '';
|
|
1542
|
-
const text = item && typeof item.text === 'string' ? item.text : '';
|
|
1543
|
-
const isSystemRole = role === 'system';
|
|
1533
|
+
function removeLeadingSystemMessage(messages) {
|
|
1534
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
1535
|
+
return [];
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
let startIndex = 0;
|
|
1539
|
+
while (startIndex < messages.length) {
|
|
1540
|
+
const item = messages[startIndex];
|
|
1541
|
+
const role = item ? normalizeRole(item.role) : '';
|
|
1542
|
+
const text = item && typeof item.text === 'string' ? item.text : '';
|
|
1543
|
+
const isSystemRole = role === 'system';
|
|
1544
1544
|
const isBootstrapText = isBootstrapLikeText(text);
|
|
1545
1545
|
if (!item || isSystemRole || isBootstrapText) {
|
|
1546
1546
|
startIndex += 1;
|
|
@@ -1627,16 +1627,85 @@ function matchesSessionPathFilter(session, normalizedFilter) {
|
|
|
1627
1627
|
return cwd.includes(normalizedFilter);
|
|
1628
1628
|
}
|
|
1629
1629
|
|
|
1630
|
-
function normalizeQueryTokens(query) {
|
|
1631
|
-
if (typeof query !== 'string') {
|
|
1632
|
-
return [];
|
|
1633
|
-
}
|
|
1634
|
-
return query
|
|
1635
|
-
.split(/\s+/)
|
|
1636
|
-
.map(item => item.trim())
|
|
1637
|
-
.map(item => item.toLowerCase())
|
|
1638
|
-
.filter(Boolean);
|
|
1639
|
-
}
|
|
1630
|
+
function normalizeQueryTokens(query) {
|
|
1631
|
+
if (typeof query !== 'string') {
|
|
1632
|
+
return [];
|
|
1633
|
+
}
|
|
1634
|
+
return query
|
|
1635
|
+
.split(/\s+/)
|
|
1636
|
+
.map(item => item.trim())
|
|
1637
|
+
.map(item => item.toLowerCase())
|
|
1638
|
+
.filter(Boolean);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
function expandSessionQueryTokens(tokens) {
|
|
1642
|
+
const base = Array.isArray(tokens) ? tokens.map(t => String(t || '').toLowerCase()).filter(Boolean) : [];
|
|
1643
|
+
const result = [];
|
|
1644
|
+
const seen = new Set();
|
|
1645
|
+
let hasClaudeAlias = false;
|
|
1646
|
+
let hasDaudeAlias = false;
|
|
1647
|
+
|
|
1648
|
+
for (const token of base) {
|
|
1649
|
+
if (/^claude[-_ ]?code$/.test(token) || token === 'claudecode') {
|
|
1650
|
+
hasClaudeAlias = true;
|
|
1651
|
+
continue;
|
|
1652
|
+
}
|
|
1653
|
+
if (/^daude[-_ ]?code$/.test(token) || token === 'daudecode') {
|
|
1654
|
+
hasDaudeAlias = true;
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
if (!seen.has(token)) {
|
|
1658
|
+
seen.add(token);
|
|
1659
|
+
result.push(token);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
const push = (token) => {
|
|
1664
|
+
const normalized = String(token || '').toLowerCase();
|
|
1665
|
+
if (!normalized || seen.has(normalized)) return;
|
|
1666
|
+
seen.add(normalized);
|
|
1667
|
+
result.push(normalized);
|
|
1668
|
+
};
|
|
1669
|
+
|
|
1670
|
+
if (hasClaudeAlias) {
|
|
1671
|
+
push('claude');
|
|
1672
|
+
push('code');
|
|
1673
|
+
}
|
|
1674
|
+
if (hasDaudeAlias) {
|
|
1675
|
+
push('daude');
|
|
1676
|
+
push('code');
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
return result;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function normalizeKeywords(value) {
|
|
1683
|
+
if (!Array.isArray(value)) {
|
|
1684
|
+
return [];
|
|
1685
|
+
}
|
|
1686
|
+
const seen = new Set();
|
|
1687
|
+
const result = [];
|
|
1688
|
+
for (const item of value) {
|
|
1689
|
+
const normalized = typeof item === 'string' ? item.trim() : String(item || '').trim();
|
|
1690
|
+
if (!normalized) continue;
|
|
1691
|
+
const lower = normalized.toLowerCase();
|
|
1692
|
+
if (seen.has(lower)) continue;
|
|
1693
|
+
seen.add(lower);
|
|
1694
|
+
result.push(normalized);
|
|
1695
|
+
}
|
|
1696
|
+
return result;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
function normalizeCapabilities(value) {
|
|
1700
|
+
const result = {};
|
|
1701
|
+
if (!value || typeof value !== 'object') {
|
|
1702
|
+
return result;
|
|
1703
|
+
}
|
|
1704
|
+
if (value.code === true) {
|
|
1705
|
+
result.code = true;
|
|
1706
|
+
}
|
|
1707
|
+
return result;
|
|
1708
|
+
}
|
|
1640
1709
|
|
|
1641
1710
|
function normalizeQueryMode(mode) {
|
|
1642
1711
|
return mode === 'or' ? 'or' : 'and';
|
|
@@ -1671,18 +1740,22 @@ function matchTokensInText(text, tokens, mode = 'and') {
|
|
|
1671
1740
|
return tokens.every(token => haystack.includes(token));
|
|
1672
1741
|
}
|
|
1673
1742
|
|
|
1674
|
-
function buildSessionSummaryText(session) {
|
|
1675
|
-
if (!session) {
|
|
1676
|
-
return '';
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
session.
|
|
1682
|
-
session.
|
|
1683
|
-
session.
|
|
1684
|
-
|
|
1685
|
-
|
|
1743
|
+
function buildSessionSummaryText(session) {
|
|
1744
|
+
if (!session) {
|
|
1745
|
+
return '';
|
|
1746
|
+
}
|
|
1747
|
+
const keywords = Array.isArray(session.keywords) ? session.keywords.join(' ') : '';
|
|
1748
|
+
const provider = typeof session.provider === 'string' ? session.provider : '';
|
|
1749
|
+
return [
|
|
1750
|
+
session.title,
|
|
1751
|
+
session.sessionId,
|
|
1752
|
+
session.cwd,
|
|
1753
|
+
session.filePath,
|
|
1754
|
+
session.sourceLabel,
|
|
1755
|
+
provider,
|
|
1756
|
+
keywords
|
|
1757
|
+
].filter(Boolean).join(' ');
|
|
1758
|
+
}
|
|
1686
1759
|
|
|
1687
1760
|
function extractMessageFromRecord(record, source) {
|
|
1688
1761
|
if (!record) {
|
|
@@ -1792,39 +1865,39 @@ function applySessionQueryFilter(sessions, options = {}) {
|
|
|
1792
1865
|
? Math.max(1024, Number(options.contentScanBytes))
|
|
1793
1866
|
: SESSION_CONTENT_READ_BYTES;
|
|
1794
1867
|
|
|
1795
|
-
let scanned = 0;
|
|
1796
|
-
const results = [];
|
|
1797
|
-
|
|
1798
|
-
for (const session of sessions) {
|
|
1799
|
-
if (scope === 'content' && scanned >= contentScanLimit) {
|
|
1868
|
+
let scanned = 0;
|
|
1869
|
+
const results = [];
|
|
1870
|
+
|
|
1871
|
+
for (const session of sessions) {
|
|
1872
|
+
if (scope === 'content' && scanned >= contentScanLimit) {
|
|
1800
1873
|
break;
|
|
1801
1874
|
}
|
|
1802
|
-
|
|
1803
|
-
const summaryText = buildSessionSummaryText(session);
|
|
1804
|
-
const summaryHit = scope !== 'content' && matchTokensInText(summaryText, tokens, mode);
|
|
1805
|
-
let contentHit = false;
|
|
1806
|
-
let contentInfo = null;
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
? summaryHit
|
|
1824
|
-
|
|
1825
|
-
if (!hit) {
|
|
1826
|
-
continue;
|
|
1827
|
-
}
|
|
1875
|
+
|
|
1876
|
+
const summaryText = buildSessionSummaryText(session);
|
|
1877
|
+
const summaryHit = scope !== 'content' && matchTokensInText(summaryText, tokens, mode);
|
|
1878
|
+
let contentHit = false;
|
|
1879
|
+
let contentInfo = null;
|
|
1880
|
+
|
|
1881
|
+
const shouldScanContent = scope === 'content' || scope === 'all' || !summaryHit;
|
|
1882
|
+
if (shouldScanContent && scanned < contentScanLimit) {
|
|
1883
|
+
scanned += 1;
|
|
1884
|
+
contentInfo = scanSessionContentForQuery(session, tokens, {
|
|
1885
|
+
mode,
|
|
1886
|
+
roleFilter,
|
|
1887
|
+
maxBytes: contentScanBytes,
|
|
1888
|
+
maxMatches: 1,
|
|
1889
|
+
snippetLimit: 2
|
|
1890
|
+
});
|
|
1891
|
+
contentHit = contentInfo.hit;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
const hit = scope === 'summary'
|
|
1895
|
+
? summaryHit
|
|
1896
|
+
: (scope === 'content' ? contentHit : (summaryHit || contentHit));
|
|
1897
|
+
|
|
1898
|
+
if (!hit) {
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1828
1901
|
|
|
1829
1902
|
const matchInfo = contentInfo && contentInfo.hit
|
|
1830
1903
|
? contentInfo
|
|
@@ -1999,23 +2072,26 @@ function parseCodexSessionSummary(filePath) {
|
|
|
1999
2072
|
}
|
|
2000
2073
|
}
|
|
2001
2074
|
|
|
2002
|
-
messageCount = Math.max(0, messageCount);
|
|
2003
|
-
|
|
2004
|
-
return {
|
|
2005
|
-
source: 'codex',
|
|
2006
|
-
sourceLabel: 'Codex',
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2075
|
+
messageCount = Math.max(0, messageCount);
|
|
2076
|
+
|
|
2077
|
+
return {
|
|
2078
|
+
source: 'codex',
|
|
2079
|
+
sourceLabel: 'Codex',
|
|
2080
|
+
provider: 'codex',
|
|
2081
|
+
sessionId,
|
|
2082
|
+
title: firstPrompt || sessionId,
|
|
2083
|
+
cwd,
|
|
2084
|
+
createdAt,
|
|
2085
|
+
updatedAt,
|
|
2086
|
+
messageCount,
|
|
2087
|
+
filePath,
|
|
2088
|
+
keywords: [],
|
|
2089
|
+
capabilities: {}
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
function parseClaudeSessionSummary(filePath) {
|
|
2094
|
+
const records = parseJsonlHeadRecords(filePath);
|
|
2019
2095
|
if (records.length === 0) {
|
|
2020
2096
|
return null;
|
|
2021
2097
|
}
|
|
@@ -2085,20 +2161,23 @@ function parseClaudeSessionSummary(filePath) {
|
|
|
2085
2161
|
}
|
|
2086
2162
|
}
|
|
2087
2163
|
|
|
2088
|
-
messageCount = Math.max(0, messageCount);
|
|
2089
|
-
|
|
2090
|
-
return {
|
|
2091
|
-
source: 'claude',
|
|
2092
|
-
sourceLabel: 'Claude Code',
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2164
|
+
messageCount = Math.max(0, messageCount);
|
|
2165
|
+
|
|
2166
|
+
return {
|
|
2167
|
+
source: 'claude',
|
|
2168
|
+
sourceLabel: 'Claude Code',
|
|
2169
|
+
provider: 'claude',
|
|
2170
|
+
sessionId,
|
|
2171
|
+
title: firstPrompt || sessionId,
|
|
2172
|
+
cwd,
|
|
2173
|
+
createdAt,
|
|
2174
|
+
updatedAt,
|
|
2175
|
+
messageCount,
|
|
2176
|
+
filePath,
|
|
2177
|
+
keywords: [],
|
|
2178
|
+
capabilities: { code: true }
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2102
2181
|
|
|
2103
2182
|
function listCodexSessions(limit, options = {}) {
|
|
2104
2183
|
const codexSessionsDir = getCodexSessionsDir();
|
|
@@ -2199,12 +2278,12 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
2199
2278
|
let title = truncateText(entry.summary || entry.firstPrompt || sessionId, 120);
|
|
2200
2279
|
let messageCount = Number.isFinite(entry.messageCount) ? Math.max(0, entry.messageCount - 1) : 0;
|
|
2201
2280
|
|
|
2202
|
-
const quickRecords = parseJsonlHeadRecords(filePath, SESSION_TITLE_READ_BYTES);
|
|
2203
|
-
if (quickRecords.length > 0) {
|
|
2204
|
-
const filteredCount = countConversationMessagesInRecords(quickRecords, 'claude');
|
|
2205
|
-
if (filteredCount > 0 || messageCount === 0) {
|
|
2206
|
-
messageCount = filteredCount;
|
|
2207
|
-
}
|
|
2281
|
+
const quickRecords = parseJsonlHeadRecords(filePath, SESSION_TITLE_READ_BYTES);
|
|
2282
|
+
if (quickRecords.length > 0) {
|
|
2283
|
+
const filteredCount = countConversationMessagesInRecords(quickRecords, 'claude');
|
|
2284
|
+
if (filteredCount > 0 || messageCount === 0) {
|
|
2285
|
+
messageCount = filteredCount;
|
|
2286
|
+
}
|
|
2208
2287
|
|
|
2209
2288
|
const quickMessages = [];
|
|
2210
2289
|
for (const record of quickRecords) {
|
|
@@ -2213,29 +2292,38 @@ function listClaudeSessions(limit, options = {}) {
|
|
|
2213
2292
|
const content = record.message ? record.message.content : '';
|
|
2214
2293
|
quickMessages.push({ role, text: extractMessageText(content) });
|
|
2215
2294
|
}
|
|
2216
|
-
}
|
|
2217
|
-
const filteredQuickMessages = removeLeadingSystemMessage(quickMessages);
|
|
2218
|
-
const firstUser = filteredQuickMessages.find(item => item.role === 'user' && item.text);
|
|
2219
|
-
if (firstUser) {
|
|
2220
|
-
title = truncateText(firstUser.text, 120);
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2295
|
+
}
|
|
2296
|
+
const filteredQuickMessages = removeLeadingSystemMessage(quickMessages);
|
|
2297
|
+
const firstUser = filteredQuickMessages.find(item => item.role === 'user' && item.text);
|
|
2298
|
+
if (firstUser) {
|
|
2299
|
+
title = truncateText(firstUser.text, 120);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
const provider = typeof entry.provider === 'string' && entry.provider.trim()
|
|
2304
|
+
? entry.provider.trim()
|
|
2305
|
+
: 'claude';
|
|
2306
|
+
const keywords = normalizeKeywords(entry.keywords);
|
|
2307
|
+
const capabilities = normalizeCapabilities(entry.capabilities);
|
|
2308
|
+
|
|
2309
|
+
sessions.push({
|
|
2310
|
+
source: 'claude',
|
|
2311
|
+
sourceLabel: 'Claude Code',
|
|
2312
|
+
provider,
|
|
2313
|
+
sessionId,
|
|
2314
|
+
title,
|
|
2315
|
+
cwd: entry.projectPath || index.originalPath || '',
|
|
2316
|
+
createdAt,
|
|
2317
|
+
updatedAt,
|
|
2318
|
+
messageCount,
|
|
2319
|
+
filePath,
|
|
2320
|
+
keywords,
|
|
2321
|
+
capabilities
|
|
2322
|
+
});
|
|
2323
|
+
|
|
2324
|
+
if (sessions.length >= targetCount) {
|
|
2325
|
+
break;
|
|
2326
|
+
}
|
|
2239
2327
|
}
|
|
2240
2328
|
|
|
2241
2329
|
if (sessions.length >= targetCount) {
|
|
@@ -2268,15 +2356,15 @@ function listAllSessions(params = {}) {
|
|
|
2268
2356
|
const source = params.source === 'codex' || params.source === 'claude'
|
|
2269
2357
|
? params.source
|
|
2270
2358
|
: 'all';
|
|
2271
|
-
const rawLimit = Number(params.limit);
|
|
2272
|
-
const limit = Number.isFinite(rawLimit)
|
|
2273
|
-
? Math.max(1, Math.min(rawLimit, MAX_SESSION_LIST_SIZE))
|
|
2274
|
-
: 120;
|
|
2275
|
-
const forceRefresh = !!params.forceRefresh;
|
|
2276
|
-
const normalizedPathFilter = normalizeSessionPathFilter(params.pathFilter);
|
|
2277
|
-
const hasPathFilter = !!normalizedPathFilter;
|
|
2278
|
-
const queryTokens = normalizeQueryTokens(params.query);
|
|
2279
|
-
const hasQuery = queryTokens.length > 0;
|
|
2359
|
+
const rawLimit = Number(params.limit);
|
|
2360
|
+
const limit = Number.isFinite(rawLimit)
|
|
2361
|
+
? Math.max(1, Math.min(rawLimit, MAX_SESSION_LIST_SIZE))
|
|
2362
|
+
: 120;
|
|
2363
|
+
const forceRefresh = !!params.forceRefresh;
|
|
2364
|
+
const normalizedPathFilter = normalizeSessionPathFilter(params.pathFilter);
|
|
2365
|
+
const hasPathFilter = !!normalizedPathFilter;
|
|
2366
|
+
const queryTokens = expandSessionQueryTokens(normalizeQueryTokens(params.query));
|
|
2367
|
+
const hasQuery = queryTokens.length > 0;
|
|
2280
2368
|
const cacheKey = hasQuery ? '' : `${source}:${limit}:${normalizedPathFilter}`;
|
|
2281
2369
|
if (!hasQuery) {
|
|
2282
2370
|
const cached = getSessionListCache(cacheKey, forceRefresh);
|
|
@@ -2293,16 +2381,16 @@ function listAllSessions(params = {}) {
|
|
|
2293
2381
|
: {};
|
|
2294
2382
|
|
|
2295
2383
|
let sessions = [];
|
|
2296
|
-
if (source === 'all' || source === 'codex') {
|
|
2297
|
-
sessions = sessions.concat(listCodexSessions(limit, scanOptions));
|
|
2298
|
-
}
|
|
2299
|
-
if (source === 'all' || source === 'claude') {
|
|
2300
|
-
sessions = sessions.concat(listClaudeSessions(limit, scanOptions));
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
if (hasPathFilter) {
|
|
2304
|
-
sessions = sessions.filter(item => matchesSessionPathFilter(item, normalizedPathFilter));
|
|
2305
|
-
}
|
|
2384
|
+
if (source === 'all' || source === 'codex') {
|
|
2385
|
+
sessions = sessions.concat(listCodexSessions(limit, scanOptions));
|
|
2386
|
+
}
|
|
2387
|
+
if (source === 'all' || source === 'claude') {
|
|
2388
|
+
sessions = sessions.concat(listClaudeSessions(limit, scanOptions));
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
if (hasPathFilter) {
|
|
2392
|
+
sessions = sessions.filter(item => matchesSessionPathFilter(item, normalizedPathFilter));
|
|
2393
|
+
}
|
|
2306
2394
|
|
|
2307
2395
|
let result = sessions;
|
|
2308
2396
|
if (hasQuery) {
|
|
@@ -2389,15 +2477,15 @@ function resolveSessionFilePath(source, filePath, sessionId) {
|
|
|
2389
2477
|
}
|
|
2390
2478
|
}
|
|
2391
2479
|
|
|
2392
|
-
if (typeof sessionId === 'string' && sessionId.trim()) {
|
|
2393
|
-
const targetId = sessionId.trim().toLowerCase();
|
|
2394
|
-
const files = collectJsonlFiles(root, 5000);
|
|
2395
|
-
const matchedFile = files.find(item => path.basename(item).toLowerCase()
|
|
2396
|
-
if (matchedFile && fs.existsSync(matchedFile)) {
|
|
2397
|
-
return matchedFile;
|
|
2398
|
-
}
|
|
2399
|
-
}
|
|
2400
|
-
|
|
2480
|
+
if (typeof sessionId === 'string' && sessionId.trim()) {
|
|
2481
|
+
const targetId = sessionId.trim().toLowerCase();
|
|
2482
|
+
const files = collectJsonlFiles(root, 5000);
|
|
2483
|
+
const matchedFile = files.find(item => path.basename(item, '.jsonl').toLowerCase() === targetId);
|
|
2484
|
+
if (matchedFile && fs.existsSync(matchedFile)) {
|
|
2485
|
+
return matchedFile;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2401
2489
|
return '';
|
|
2402
2490
|
}
|
|
2403
2491
|
|
|
@@ -4483,13 +4571,16 @@ function formatHostForUrl(host) {
|
|
|
4483
4571
|
// 打开 Web UI
|
|
4484
4572
|
function cmdStart(options = {}) {
|
|
4485
4573
|
const htmlPath = path.join(__dirname, 'web-ui.html');
|
|
4574
|
+
const assetsDir = path.join(__dirname, 'res');
|
|
4575
|
+
const webDir = path.join(__dirname, 'web-ui');
|
|
4486
4576
|
if (!fs.existsSync(htmlPath)) {
|
|
4487
4577
|
console.error('错误: web-ui.html 不存在');
|
|
4488
4578
|
process.exit(1);
|
|
4489
4579
|
}
|
|
4490
4580
|
|
|
4491
4581
|
const server = http.createServer((req, res) => {
|
|
4492
|
-
|
|
4582
|
+
const requestPath = (req.url || '/').split('?')[0];
|
|
4583
|
+
if (requestPath === '/api') {
|
|
4493
4584
|
let body = '';
|
|
4494
4585
|
req.on('data', chunk => body += chunk);
|
|
4495
4586
|
req.on('end', async () => {
|
|
@@ -4670,6 +4761,50 @@ function cmdStart(options = {}) {
|
|
|
4670
4761
|
res.end(JSON.stringify({ error: e.message }));
|
|
4671
4762
|
}
|
|
4672
4763
|
});
|
|
4764
|
+
} else if (requestPath.startsWith('/web-ui/')) {
|
|
4765
|
+
const normalized = path.normalize(requestPath).replace(/^([\\.\\/])+/, '');
|
|
4766
|
+
const filePath = path.join(__dirname, normalized);
|
|
4767
|
+
if (!isPathInside(filePath, webDir)) {
|
|
4768
|
+
res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
4769
|
+
res.end('Forbidden');
|
|
4770
|
+
return;
|
|
4771
|
+
}
|
|
4772
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
4773
|
+
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
4774
|
+
res.end('Not Found');
|
|
4775
|
+
return;
|
|
4776
|
+
}
|
|
4777
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
4778
|
+
const mime = ext === '.js' || ext === '.mjs'
|
|
4779
|
+
? 'application/javascript; charset=utf-8'
|
|
4780
|
+
: ext === '.css'
|
|
4781
|
+
? 'text/css; charset=utf-8'
|
|
4782
|
+
: ext === '.json'
|
|
4783
|
+
? 'application/json; charset=utf-8'
|
|
4784
|
+
: 'application/octet-stream';
|
|
4785
|
+
res.writeHead(200, { 'Content-Type': mime });
|
|
4786
|
+
fs.createReadStream(filePath).pipe(res);
|
|
4787
|
+
} else if (requestPath.startsWith('/res/')) {
|
|
4788
|
+
const normalized = path.normalize(requestPath).replace(/^([\\.\\/])+/, '');
|
|
4789
|
+
const filePath = path.join(__dirname, normalized);
|
|
4790
|
+
if (!isPathInside(filePath, assetsDir)) {
|
|
4791
|
+
res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
4792
|
+
res.end('Forbidden');
|
|
4793
|
+
return;
|
|
4794
|
+
}
|
|
4795
|
+
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
|
4796
|
+
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
4797
|
+
res.end('Not Found');
|
|
4798
|
+
return;
|
|
4799
|
+
}
|
|
4800
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
4801
|
+
const mime = ext === '.js'
|
|
4802
|
+
? 'application/javascript; charset=utf-8'
|
|
4803
|
+
: ext === '.json'
|
|
4804
|
+
? 'application/json; charset=utf-8'
|
|
4805
|
+
: 'application/octet-stream';
|
|
4806
|
+
res.writeHead(200, { 'Content-Type': mime });
|
|
4807
|
+
fs.createReadStream(filePath).pipe(res);
|
|
4673
4808
|
} else {
|
|
4674
4809
|
const html = fs.readFileSync(htmlPath, 'utf-8');
|
|
4675
4810
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
package/lib/cli-file-utils.js
CHANGED
|
@@ -111,15 +111,13 @@ function writeJsonAtomic(filePath, data) {
|
|
|
111
111
|
|
|
112
112
|
const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
113
113
|
const content = `${JSON.stringify(data, null, 2)}\n`;
|
|
114
|
+
const hasExisting = fs.existsSync(filePath);
|
|
115
|
+
const targetMode = hasExisting ? (fs.statSync(filePath).mode & 0o777) : 0o600;
|
|
114
116
|
|
|
115
117
|
try {
|
|
116
|
-
fs.writeFileSync(tmpPath, content, 'utf-8');
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
fs.chmodSync(tmpPath, existingMode);
|
|
120
|
-
} else {
|
|
121
|
-
fs.chmodSync(tmpPath, 0o600);
|
|
122
|
-
}
|
|
118
|
+
fs.writeFileSync(tmpPath, content, { encoding: 'utf-8', mode: targetMode });
|
|
119
|
+
fs.chmodSync(tmpPath, targetMode);
|
|
120
|
+
|
|
123
121
|
try {
|
|
124
122
|
fs.renameSync(tmpPath, filePath);
|
|
125
123
|
} catch (renameError) {
|
|
@@ -130,6 +128,10 @@ function writeJsonAtomic(filePath, data) {
|
|
|
130
128
|
throw renameError;
|
|
131
129
|
}
|
|
132
130
|
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
fs.chmodSync(filePath, targetMode);
|
|
134
|
+
} catch (_) {}
|
|
133
135
|
} catch (e) {
|
|
134
136
|
if (fs.existsSync(tmpPath)) {
|
|
135
137
|
try { fs.unlinkSync(tmpPath); } catch (_) {}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codexmate",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "Codex/Claude Code 配置与会话管理 CLI + Web 工具",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codexmate": "./cli.js"
|
|
7
7
|
},
|
|
8
8
|
"scripts": {
|
|
9
9
|
"start": "node cli.js",
|
|
10
|
-
"test": "npm run test:e2e",
|
|
10
|
+
"test": "npm run test:unit && npm run test:e2e",
|
|
11
|
+
"test:unit": "node tests/unit/run.mjs",
|
|
11
12
|
"test:e2e": "node tests/e2e/run.js"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|