codexmate 0.0.25 → 0.0.27
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.md +11 -3
- package/README.zh.md +10 -2
- package/cli/builtin-proxy.js +315 -95
- package/cli/openai-bridge.js +99 -5
- package/cli/session-convert-args.js +65 -0
- package/cli/session-convert-io.js +82 -0
- package/cli/session-convert.js +43 -0
- package/cli.js +547 -32
- package/package.json +74 -74
- package/web-ui/app.js +24 -2
- package/web-ui/logic.session-convert.mjs +70 -0
- package/web-ui/logic.sessions.mjs +151 -0
- package/web-ui/modules/app.computed.dashboard.mjs +44 -1
- package/web-ui/modules/app.computed.session.mjs +336 -12
- package/web-ui/modules/app.methods.claude-config.mjs +11 -1
- package/web-ui/modules/app.methods.codex-config.mjs +76 -0
- package/web-ui/modules/app.methods.navigation.mjs +51 -3
- package/web-ui/modules/app.methods.session-actions.mjs +55 -3
- package/web-ui/modules/app.methods.session-browser.mjs +270 -3
- package/web-ui/modules/app.methods.session-timeline.mjs +34 -3
- package/web-ui/modules/app.methods.session-trash.mjs +16 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +234 -125
- package/web-ui/modules/i18n.dict.mjs +76 -0
- package/web-ui/partials/index/panel-config-claude.html +12 -4
- package/web-ui/partials/index/panel-sessions.html +33 -10
- package/web-ui/partials/index/panel-settings.html +16 -0
- package/web-ui/partials/index/panel-usage.html +95 -85
- package/web-ui/session-helpers.mjs +3 -0
- package/web-ui/styles/base-theme.css +29 -25
- package/web-ui/styles/layout-shell.css +1 -1
- package/web-ui/styles/navigation-panels.css +9 -9
- package/web-ui/styles/sessions-list.css +17 -0
- package/web-ui/styles/sessions-toolbar-trash.css +62 -0
- package/web-ui/styles/sessions-usage.css +211 -83
- package/web-ui/styles/settings-panel.css +19 -0
package/package.json
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "codexmate",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
|
|
5
|
-
"main": "cli.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"codexmate": "./cli.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"cli.js",
|
|
11
|
-
"cli/",
|
|
12
|
-
"plugins/",
|
|
13
|
-
"web-ui.html",
|
|
14
|
-
"lib/",
|
|
15
|
-
"web-ui/",
|
|
16
|
-
"doc/",
|
|
17
|
-
"README.md",
|
|
18
|
-
"README.zh.md",
|
|
19
|
-
"LICENSE"
|
|
20
|
-
],
|
|
21
|
-
"exports": {
|
|
22
|
-
".": "./cli.js",
|
|
23
|
-
"./lib/*": "./lib/*.js",
|
|
24
|
-
"./web-ui/*": "./web-ui/*",
|
|
25
|
-
"./res/*": "./web-ui/res/*"
|
|
26
|
-
},
|
|
27
|
-
"scripts": {
|
|
28
|
-
"dev": "node cli.js run",
|
|
29
|
-
"start": "node cli.js",
|
|
30
|
-
"reset": "node tools/dev/reset-main.js",
|
|
31
|
-
"release:npm": "node tools/release/publish-npm.js",
|
|
32
|
-
"docs:dev": "node ./node_modules/vitepress/dist/node/cli.js dev site",
|
|
33
|
-
"docs:build": "node ./node_modules/vitepress/dist/node/cli.js build site",
|
|
34
|
-
"docs:preview": "node ./node_modules/vitepress/dist/node/cli.js preview site",
|
|
35
|
-
"ci:install": "node tools/ci/run-check.js install",
|
|
36
|
-
"ci:lint": "node tools/ci/run-check.js lint",
|
|
37
|
-
"ci:test": "node tools/ci/run-check.js test",
|
|
38
|
-
"lint": "node tools/dev/lint.js",
|
|
39
|
-
"test": "npm run test:unit && npm run test:e2e",
|
|
40
|
-
"test:ci": "node tools/ci/run-check.js all",
|
|
41
|
-
"test:unit": "node tests/unit/run.mjs",
|
|
42
|
-
"test:e2e": "node tests/e2e/run.js",
|
|
43
|
-
"pretest": "node tools/ci/ensure-test-deps.js"
|
|
44
|
-
},
|
|
45
|
-
"dependencies": {
|
|
46
|
-
"@iarna/toml": "^2.2.5",
|
|
47
|
-
"json5": "^2.2.3",
|
|
48
|
-
"yauzl": "^3.2.1",
|
|
49
|
-
"zip-lib": "^1.2.1"
|
|
50
|
-
},
|
|
51
|
-
"engines": {
|
|
52
|
-
"node": ">=14"
|
|
53
|
-
},
|
|
54
|
-
"keywords": [
|
|
55
|
-
"codex",
|
|
56
|
-
"claude",
|
|
57
|
-
"claude-code",
|
|
58
|
-
"openclaw",
|
|
59
|
-
"config",
|
|
60
|
-
"session",
|
|
61
|
-
"task-orchestration",
|
|
62
|
-
"workflow",
|
|
63
|
-
"web-ui",
|
|
64
|
-
"provider",
|
|
65
|
-
"ai",
|
|
66
|
-
"llm",
|
|
67
|
-
"cli"
|
|
68
|
-
],
|
|
69
|
-
"author": "ymkiux",
|
|
70
|
-
"license": "Apache-2.0",
|
|
71
|
-
"devDependencies": {
|
|
72
|
-
"vitepress": "^1.6.4"
|
|
73
|
-
}
|
|
74
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "codexmate",
|
|
3
|
+
"version": "0.0.27",
|
|
4
|
+
"description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
|
|
5
|
+
"main": "cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"codexmate": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli.js",
|
|
11
|
+
"cli/",
|
|
12
|
+
"plugins/",
|
|
13
|
+
"web-ui.html",
|
|
14
|
+
"lib/",
|
|
15
|
+
"web-ui/",
|
|
16
|
+
"doc/",
|
|
17
|
+
"README.md",
|
|
18
|
+
"README.zh.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./cli.js",
|
|
23
|
+
"./lib/*": "./lib/*.js",
|
|
24
|
+
"./web-ui/*": "./web-ui/*",
|
|
25
|
+
"./res/*": "./web-ui/res/*"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "node cli.js run",
|
|
29
|
+
"start": "node cli.js",
|
|
30
|
+
"reset": "node tools/dev/reset-main.js",
|
|
31
|
+
"release:npm": "node tools/release/publish-npm.js",
|
|
32
|
+
"docs:dev": "node ./node_modules/vitepress/dist/node/cli.js dev site",
|
|
33
|
+
"docs:build": "node ./node_modules/vitepress/dist/node/cli.js build site",
|
|
34
|
+
"docs:preview": "node ./node_modules/vitepress/dist/node/cli.js preview site",
|
|
35
|
+
"ci:install": "node tools/ci/run-check.js install",
|
|
36
|
+
"ci:lint": "node tools/ci/run-check.js lint",
|
|
37
|
+
"ci:test": "node tools/ci/run-check.js test",
|
|
38
|
+
"lint": "node tools/dev/lint.js",
|
|
39
|
+
"test": "npm run test:unit && npm run test:e2e",
|
|
40
|
+
"test:ci": "node tools/ci/run-check.js all",
|
|
41
|
+
"test:unit": "node tests/unit/run.mjs",
|
|
42
|
+
"test:e2e": "node tests/e2e/run.js",
|
|
43
|
+
"pretest": "node tools/ci/ensure-test-deps.js"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@iarna/toml": "^2.2.5",
|
|
47
|
+
"json5": "^2.2.3",
|
|
48
|
+
"yauzl": "^3.2.1",
|
|
49
|
+
"zip-lib": "^1.2.1"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=14"
|
|
53
|
+
},
|
|
54
|
+
"keywords": [
|
|
55
|
+
"codex",
|
|
56
|
+
"claude",
|
|
57
|
+
"claude-code",
|
|
58
|
+
"openclaw",
|
|
59
|
+
"config",
|
|
60
|
+
"session",
|
|
61
|
+
"task-orchestration",
|
|
62
|
+
"workflow",
|
|
63
|
+
"web-ui",
|
|
64
|
+
"provider",
|
|
65
|
+
"ai",
|
|
66
|
+
"llm",
|
|
67
|
+
"cli"
|
|
68
|
+
],
|
|
69
|
+
"author": "ymkiux",
|
|
70
|
+
"license": "Apache-2.0",
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"vitepress": "^1.6.4"
|
|
73
|
+
}
|
|
74
|
+
}
|
package/web-ui/app.js
CHANGED
|
@@ -112,7 +112,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
112
112
|
_pendingCodexApplyOptions: null,
|
|
113
113
|
agentsContent: '',
|
|
114
114
|
agentsPath: '',
|
|
115
|
-
agentsPath: '',
|
|
116
115
|
agentsExists: false,
|
|
117
116
|
agentsLineEnding: '\n',
|
|
118
117
|
agentsLoading: false,
|
|
@@ -158,8 +157,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
158
157
|
ticket: 0
|
|
159
158
|
},
|
|
160
159
|
sessionsViewMode: 'browser',
|
|
161
|
-
sessionsUsageTimeRange: '7d',
|
|
160
|
+
sessionsUsageTimeRange: (function () { try { const saved = localStorage.getItem('sessionsUsageTimeRange'); if (saved === '7d' || saved === '30d' || saved === 'all') return saved; } catch (_) {} return '7d'; })(),
|
|
162
161
|
sessionsUsageList: [],
|
|
162
|
+
sessionsUsageCompareEnabled: false,
|
|
163
|
+
sessionsUsageSelectedDayKey: '',
|
|
163
164
|
sessionsUsageLoadedOnce: false,
|
|
164
165
|
sessionsUsageLoadedLimit: 0,
|
|
165
166
|
sessionsUsageLoading: false,
|
|
@@ -172,6 +173,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
172
173
|
sessionQuery: '',
|
|
173
174
|
sessionRoleFilter: 'all',
|
|
174
175
|
sessionTimePreset: 'all',
|
|
176
|
+
sessionSortMode: 'time',
|
|
175
177
|
sessionResumeWithYolo: true,
|
|
176
178
|
sessionPathOptions: [],
|
|
177
179
|
sessionPathOptionsLoading: false,
|
|
@@ -194,6 +196,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
194
196
|
gemini: 0
|
|
195
197
|
},
|
|
196
198
|
sessionExporting: {},
|
|
199
|
+
sessionConverting: {},
|
|
197
200
|
sessionCloning: {},
|
|
198
201
|
sessionDeleting: {},
|
|
199
202
|
activeSession: null,
|
|
@@ -261,6 +264,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
261
264
|
newModelName: '',
|
|
262
265
|
currentClaudeConfig: '',
|
|
263
266
|
currentClaudeModel: '',
|
|
267
|
+
claudeCustomModelDraft: '',
|
|
264
268
|
editingConfig: { name: '', apiKey: '', baseUrl: '', model: '' },
|
|
265
269
|
claudeConfigs: {
|
|
266
270
|
'智谱GLM': {
|
|
@@ -358,6 +362,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
358
362
|
sessionTrashRestoring: {},
|
|
359
363
|
sessionTrashPurging: {},
|
|
360
364
|
sessionTrashClearing: false,
|
|
365
|
+
sessionTrashRetentionDays: 30,
|
|
361
366
|
claudeImportLoading: false,
|
|
362
367
|
codexImportLoading: false,
|
|
363
368
|
codexAuthProfiles: [],
|
|
@@ -474,6 +479,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
474
479
|
this.restoreSessionPinnedMap();
|
|
475
480
|
this.shareCommandPrefix = this.normalizeShareCommandPrefix(localStorage.getItem('codexmateShareCommandPrefix'));
|
|
476
481
|
this.sessionTrashEnabled = this.normalizeSessionTrashEnabled(localStorage.getItem('codexmateSessionTrashEnabled'));
|
|
482
|
+
this.sessionTrashRetentionDays = this.normalizeSessionTrashRetentionDays(localStorage.getItem('codexmateSessionTrashRetentionDays'));
|
|
477
483
|
this.configTemplateDiffConfirmEnabled = loadConfigTemplateDiffConfirmEnabledFromStorage(localStorage);
|
|
478
484
|
window.addEventListener('resize', this.onWindowResize);
|
|
479
485
|
window.addEventListener('keydown', this.handleGlobalKeydown);
|
|
@@ -493,6 +499,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
493
499
|
console.error('加载 Claude 配置失败:', e);
|
|
494
500
|
}
|
|
495
501
|
}
|
|
502
|
+
{
|
|
503
|
+
const savedCurrentClaudeConfig = localStorage.getItem('currentClaudeConfig');
|
|
504
|
+
if (savedCurrentClaudeConfig && this.claudeConfigs[savedCurrentClaudeConfig]) {
|
|
505
|
+
this.currentClaudeConfig = savedCurrentClaudeConfig;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (!this.currentClaudeConfig) {
|
|
509
|
+
const claudeConfigNames = Object.keys(this.claudeConfigs || {});
|
|
510
|
+
if (claudeConfigNames.length > 0) {
|
|
511
|
+
this.currentClaudeConfig = claudeConfigNames[0];
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (this.currentClaudeConfig && !this.currentClaudeModel) {
|
|
515
|
+
const initialClaudeConfig = this.claudeConfigs[this.currentClaudeConfig];
|
|
516
|
+
this.currentClaudeModel = initialClaudeConfig && initialClaudeConfig.model ? initialClaudeConfig.model : '';
|
|
517
|
+
}
|
|
496
518
|
const normalizeOpenclawConfigs = (configs) => {
|
|
497
519
|
const source = configs && typeof configs === 'object' && !Array.isArray(configs)
|
|
498
520
|
? configs
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
function stripLeadingSystemMessage(messages) {
|
|
2
|
+
if (!Array.isArray(messages) || messages.length < 2) return messages;
|
|
3
|
+
const first = messages[0];
|
|
4
|
+
if (!first || first.role !== 'system') return messages;
|
|
5
|
+
return messages.slice(1);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function toIsoTimestamp(value, fallback) {
|
|
9
|
+
const text = typeof value === 'string' ? value.trim() : '';
|
|
10
|
+
if (text) return text;
|
|
11
|
+
return typeof fallback === 'string' ? fallback : '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function normalizeSessionConvertSource(value) {
|
|
15
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
16
|
+
if (normalized === 'codex' || normalized === 'claude') return normalized;
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getConvertTargetSource(source) {
|
|
21
|
+
return source === 'codex' ? 'claude' : (source === 'claude' ? 'codex' : '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function buildConvertedSessionJsonl(target, payload) {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const baseTime = new Date(now).toISOString();
|
|
27
|
+
const sessionId = typeof payload.sessionId === 'string' ? payload.sessionId : '';
|
|
28
|
+
const cwd = typeof payload.cwd === 'string' ? payload.cwd : '';
|
|
29
|
+
const rawMessages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
30
|
+
const messages = stripLeadingSystemMessage(rawMessages);
|
|
31
|
+
const lines = [];
|
|
32
|
+
|
|
33
|
+
if (target === 'codex') {
|
|
34
|
+
lines.push(JSON.stringify({ type: 'session_meta', timestamp: baseTime, payload: { id: sessionId, cwd } }));
|
|
35
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
36
|
+
const message = messages[i];
|
|
37
|
+
if (!message) continue;
|
|
38
|
+
const role = message.role === 'user' || message.role === 'assistant' || message.role === 'system'
|
|
39
|
+
? message.role
|
|
40
|
+
: 'assistant';
|
|
41
|
+
const text = typeof message.text === 'string' ? message.text : '';
|
|
42
|
+
if (!text) continue;
|
|
43
|
+
lines.push(JSON.stringify({
|
|
44
|
+
type: 'response_item',
|
|
45
|
+
timestamp: toIsoTimestamp(message.timestamp, new Date(now + i).toISOString()),
|
|
46
|
+
payload: { type: 'message', role, content: text }
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
return `${lines.join('\n')}\n`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
53
|
+
const message = messages[i];
|
|
54
|
+
if (!message) continue;
|
|
55
|
+
const role = message.role === 'user' || message.role === 'assistant' || message.role === 'system'
|
|
56
|
+
? message.role
|
|
57
|
+
: 'assistant';
|
|
58
|
+
const text = typeof message.text === 'string' ? message.text : '';
|
|
59
|
+
if (!text) continue;
|
|
60
|
+
lines.push(JSON.stringify({
|
|
61
|
+
type: role,
|
|
62
|
+
timestamp: toIsoTimestamp(message.timestamp, new Date(now + i).toISOString()),
|
|
63
|
+
sessionId,
|
|
64
|
+
cwd,
|
|
65
|
+
message: { content: text }
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
return `${lines.join('\n')}\n`;
|
|
69
|
+
}
|
|
70
|
+
|
|
@@ -206,6 +206,157 @@ function formatUtcDayKey(value) {
|
|
|
206
206
|
return `${stamp.getUTCFullYear()}-${String(stamp.getUTCMonth() + 1).padStart(2, '0')}-${String(stamp.getUTCDate()).padStart(2, '0')}`;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
export function buildUsageHeatmap(sessions = [], options = {}) {
|
|
210
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
211
|
+
const normalized = [];
|
|
212
|
+
for (const session of list) {
|
|
213
|
+
if (!session || typeof session !== 'object') continue;
|
|
214
|
+
const source = normalizeSessionSource(session.source, '');
|
|
215
|
+
if (source !== 'codex' && source !== 'claude') continue;
|
|
216
|
+
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
217
|
+
if (!Number.isFinite(updatedAtMs)) continue;
|
|
218
|
+
normalized.push({
|
|
219
|
+
updatedAtMs,
|
|
220
|
+
messageCount: Number.isFinite(Number(session.messageCount))
|
|
221
|
+
? Math.max(0, Math.floor(Number(session.messageCount)))
|
|
222
|
+
: 0,
|
|
223
|
+
tokenTotal: readSessionTotalTokens(session)
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const range = normalizeUsageRange(options.range);
|
|
228
|
+
const now = Number.isFinite(Number(options.now)) ? Number(options.now) : Date.now();
|
|
229
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
230
|
+
const todayStart = toUtcDayStartMs(now);
|
|
231
|
+
let startDay = todayStart;
|
|
232
|
+
let endDay = todayStart;
|
|
233
|
+
if (range === 'all') {
|
|
234
|
+
const dayStarts = normalized.map((item) => toUtcDayStartMs(item.updatedAtMs)).filter((value) => Number.isFinite(value));
|
|
235
|
+
if (dayStarts.length) {
|
|
236
|
+
startDay = Math.min(...dayStarts);
|
|
237
|
+
endDay = Math.max(...dayStarts);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
const rangeDays = range === '30d' ? 30 : 7;
|
|
241
|
+
endDay = todayStart;
|
|
242
|
+
startDay = todayStart - ((rangeDays - 1) * dayMs);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const startDow = new Date(startDay).getUTCDay();
|
|
246
|
+
const startShift = (startDow + 6) % 7;
|
|
247
|
+
const alignedStart = startDay - (startShift * dayMs);
|
|
248
|
+
const endDow = new Date(endDay).getUTCDay();
|
|
249
|
+
const endShift = (6 - ((endDow + 6) % 7));
|
|
250
|
+
const alignedEnd = endDay + (endShift * dayMs);
|
|
251
|
+
const totalDays = Math.floor((alignedEnd - alignedStart) / dayMs) + 1;
|
|
252
|
+
const weekCount = Math.max(1, Math.ceil(totalDays / 7));
|
|
253
|
+
|
|
254
|
+
const byDay = new Map();
|
|
255
|
+
for (const item of normalized) {
|
|
256
|
+
const dayKey = formatUtcDayKey(item.updatedAtMs);
|
|
257
|
+
const existing = byDay.get(dayKey) || { sessionCount: 0, messageCount: 0, tokenTotal: 0 };
|
|
258
|
+
existing.sessionCount += 1;
|
|
259
|
+
existing.messageCount += item.messageCount;
|
|
260
|
+
existing.tokenTotal += item.tokenTotal;
|
|
261
|
+
byDay.set(dayKey, existing);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const weeks = Array.from({ length: weekCount }, (_, idx) => ({
|
|
265
|
+
key: `w-${idx}`,
|
|
266
|
+
weekIndex: idx,
|
|
267
|
+
days: Array.from({ length: 7 }, () => null)
|
|
268
|
+
}));
|
|
269
|
+
|
|
270
|
+
let maxSessionCount = 0;
|
|
271
|
+
for (let dayIndex = 0; dayIndex < totalDays; dayIndex += 1) {
|
|
272
|
+
const dateMs = alignedStart + (dayIndex * dayMs);
|
|
273
|
+
const dateKey = formatUtcDayKey(dateMs);
|
|
274
|
+
const isInRange = dateMs >= startDay && dateMs <= endDay;
|
|
275
|
+
const weekIndex = Math.floor(dayIndex / 7);
|
|
276
|
+
const dow = new Date(dateMs).getUTCDay();
|
|
277
|
+
const rowIndex = (dow + 6) % 7;
|
|
278
|
+
const totals = isInRange ? (byDay.get(dateKey) || { sessionCount: 0, messageCount: 0, tokenTotal: 0 }) : null;
|
|
279
|
+
const sessionCount = totals ? totals.sessionCount : 0;
|
|
280
|
+
if (isInRange) {
|
|
281
|
+
maxSessionCount = Math.max(maxSessionCount, sessionCount);
|
|
282
|
+
}
|
|
283
|
+
weeks[weekIndex].days[rowIndex] = {
|
|
284
|
+
dateKey,
|
|
285
|
+
dateMs,
|
|
286
|
+
isInRange,
|
|
287
|
+
sessionCount,
|
|
288
|
+
messageCount: totals ? totals.messageCount : 0,
|
|
289
|
+
tokenTotal: totals ? totals.tokenTotal : 0
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
range,
|
|
295
|
+
startDay,
|
|
296
|
+
endDay,
|
|
297
|
+
alignedStart,
|
|
298
|
+
alignedEnd,
|
|
299
|
+
maxSessionCount,
|
|
300
|
+
weeks
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function buildUsageHourlyHeatmap(sessions = [], options = {}) {
|
|
305
|
+
const list = Array.isArray(sessions) ? sessions : [];
|
|
306
|
+
const range = normalizeUsageRange(options.range);
|
|
307
|
+
const now = Number.isFinite(Number(options.now)) ? Number(options.now) : Date.now();
|
|
308
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
309
|
+
const todayStart = toUtcDayStartMs(now);
|
|
310
|
+
|
|
311
|
+
const normalized = [];
|
|
312
|
+
for (const session of list) {
|
|
313
|
+
if (!session || typeof session !== 'object') continue;
|
|
314
|
+
const source = normalizeSessionSource(session.source, '');
|
|
315
|
+
if (source !== 'codex' && source !== 'claude') continue;
|
|
316
|
+
const updatedAtMs = Date.parse(session.updatedAt || '');
|
|
317
|
+
if (!Number.isFinite(updatedAtMs)) continue;
|
|
318
|
+
const dayStart = toUtcDayStartMs(updatedAtMs);
|
|
319
|
+
if (range !== 'all') {
|
|
320
|
+
const rangeDays = range === '30d' ? 30 : 7;
|
|
321
|
+
const rangeStart = todayStart - ((rangeDays - 1) * dayMs);
|
|
322
|
+
if (dayStart < rangeStart || dayStart > todayStart) continue;
|
|
323
|
+
}
|
|
324
|
+
const stamp = new Date(updatedAtMs);
|
|
325
|
+
const weekday = (stamp.getUTCDay() + 6) % 7;
|
|
326
|
+
const hour = stamp.getUTCHours();
|
|
327
|
+
const messageCount = Number.isFinite(Number(session.messageCount))
|
|
328
|
+
? Math.max(0, Math.floor(Number(session.messageCount)))
|
|
329
|
+
: 0;
|
|
330
|
+
const tokenTotal = readSessionTotalTokens(session);
|
|
331
|
+
normalized.push({ weekday, hour, messageCount, tokenTotal });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const grid = Array.from({ length: 7 }, () =>
|
|
335
|
+
Array.from({ length: 24 }, () => ({ sessionCount: 0, messageCount: 0, tokenTotal: 0 }))
|
|
336
|
+
);
|
|
337
|
+
for (const item of normalized) {
|
|
338
|
+
const cell = grid[item.weekday][item.hour];
|
|
339
|
+
cell.sessionCount += 1;
|
|
340
|
+
cell.messageCount += item.messageCount;
|
|
341
|
+
cell.tokenTotal += item.tokenTotal;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let maxSessionCount = 0;
|
|
345
|
+
for (let day = 0; day < 7; day += 1) {
|
|
346
|
+
for (let hour = 0; hour < 24; hour += 1) {
|
|
347
|
+
maxSessionCount = Math.max(maxSessionCount, grid[day][hour].sessionCount);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
range,
|
|
353
|
+
grid,
|
|
354
|
+
maxSessionCount: Math.max(1, maxSessionCount),
|
|
355
|
+
weekdayKeys: [0, 1, 2, 3, 4, 5, 6],
|
|
356
|
+
hourLabels: Array.from({ length: 24 }, (_, index) => String(index).padStart(2, '0'))
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
209
360
|
function buildUsageBuckets(normalizedSessions, options = {}) {
|
|
210
361
|
const range = normalizeUsageRange(options.range);
|
|
211
362
|
const now = Number.isFinite(Number(options.now)) ? Number(options.now) : Date.now();
|
|
@@ -40,7 +40,50 @@ export function createDashboardComputed() {
|
|
|
40
40
|
return list;
|
|
41
41
|
},
|
|
42
42
|
installTargetCards() {
|
|
43
|
-
const targets = Array.isArray(this.installStatusTargets)
|
|
43
|
+
const targets = Array.isArray(this.installStatusTargets) && this.installStatusTargets.length
|
|
44
|
+
? this.installStatusTargets
|
|
45
|
+
: [
|
|
46
|
+
{
|
|
47
|
+
id: 'claude',
|
|
48
|
+
name: 'Claude Code CLI',
|
|
49
|
+
packageName: '@anthropic-ai/claude-code',
|
|
50
|
+
installed: false,
|
|
51
|
+
bin: 'claude',
|
|
52
|
+
version: '',
|
|
53
|
+
commandPath: '',
|
|
54
|
+
error: ''
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'codebuddy',
|
|
58
|
+
name: 'CodeBuddy Code',
|
|
59
|
+
packageName: '@tencent-ai/codebuddy-code',
|
|
60
|
+
installed: false,
|
|
61
|
+
bin: 'codebuddy',
|
|
62
|
+
version: '',
|
|
63
|
+
commandPath: '',
|
|
64
|
+
error: ''
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'gemini',
|
|
68
|
+
name: 'Gemini CLI',
|
|
69
|
+
packageName: '@google/gemini-cli',
|
|
70
|
+
installed: false,
|
|
71
|
+
bin: 'gemini',
|
|
72
|
+
version: '',
|
|
73
|
+
commandPath: '',
|
|
74
|
+
error: ''
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'codex',
|
|
78
|
+
name: 'Codex CLI',
|
|
79
|
+
packageName: '@openai/codex',
|
|
80
|
+
installed: false,
|
|
81
|
+
bin: 'codex',
|
|
82
|
+
version: '',
|
|
83
|
+
commandPath: '',
|
|
84
|
+
error: ''
|
|
85
|
+
}
|
|
86
|
+
];
|
|
44
87
|
const action = this.normalizeInstallAction(this.installCommandAction);
|
|
45
88
|
return targets.map((target) => {
|
|
46
89
|
const id = target && typeof target.id === 'string' ? target.id : '';
|