metame-cli 1.4.17 â 1.4.19
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 +118 -34
- package/index.js +12 -5
- package/package.json +2 -2
- package/scripts/check-macos-control-capabilities.sh +77 -0
- package/scripts/daemon-admin-commands.js +350 -12
- package/scripts/daemon-admin-commands.test.js +333 -0
- package/scripts/daemon-agent-commands.js +20 -1
- package/scripts/daemon-claude-engine.js +62 -12
- package/scripts/daemon-command-router.js +257 -12
- package/scripts/daemon-default.yaml +10 -3
- package/scripts/daemon-exec-commands.js +248 -13
- package/scripts/daemon-session-store.js +176 -41
- package/scripts/daemon-task-envelope.js +143 -0
- package/scripts/daemon-task-envelope.test.js +59 -0
- package/scripts/daemon-task-scheduler.js +213 -24
- package/scripts/daemon-task-scheduler.test.js +106 -0
- package/scripts/daemon-user-acl.js +399 -0
- package/scripts/daemon.js +376 -26
- package/scripts/distill.js +184 -34
- package/scripts/memory-extract.js +13 -5
- package/scripts/memory.js +239 -60
- package/scripts/providers.js +1 -1
- package/scripts/reliability-core.test.js +268 -0
- package/scripts/session-analytics.js +123 -35
- package/scripts/signal-capture.js +171 -11
- package/scripts/skill-evolution.js +158 -19
- package/scripts/task-board.js +398 -0
- package/scripts/task-board.test.js +83 -0
- package/scripts/usage-classifier.js +139 -0
- package/scripts/utils.js +107 -0
- package/scripts/utils.test.js +61 -1
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const {
|
|
4
|
+
USAGE_CATEGORY_ORDER,
|
|
5
|
+
CORE_USAGE_CATEGORIES,
|
|
6
|
+
USAGE_CATEGORY_LABEL,
|
|
7
|
+
} = require('./usage-classifier');
|
|
8
|
+
|
|
3
9
|
function createAdminCommandHandler(deps) {
|
|
4
10
|
const {
|
|
5
11
|
fs,
|
|
@@ -18,8 +24,70 @@ function createAdminCommandHandler(deps) {
|
|
|
18
24
|
dispatchTask,
|
|
19
25
|
log,
|
|
20
26
|
skillEvolution,
|
|
27
|
+
taskBoard,
|
|
28
|
+
taskEnvelope,
|
|
21
29
|
} = deps;
|
|
22
30
|
|
|
31
|
+
function resolveProjectKey(targetName, projects) {
|
|
32
|
+
if (!targetName || !projects) return null;
|
|
33
|
+
for (const [key, proj] of Object.entries(projects || {})) {
|
|
34
|
+
const nicknames = Array.isArray(proj.nicknames)
|
|
35
|
+
? proj.nicknames
|
|
36
|
+
: (proj.nicknames ? [proj.nicknames] : []);
|
|
37
|
+
if (key === targetName || nicknames.some(n => n === targetName)) return key;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveSenderKey(chatId, config) {
|
|
43
|
+
const map = {
|
|
44
|
+
...(config && config.feishu ? config.feishu.chat_agent_map : {}),
|
|
45
|
+
...(config && config.telegram ? config.telegram.chat_agent_map : {}),
|
|
46
|
+
};
|
|
47
|
+
return map[String(chatId)] || 'user';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function popFlag(input, flagName) {
|
|
51
|
+
const src = String(input || '');
|
|
52
|
+
const re = new RegExp(`(?:^|\\s)--${flagName}\\s+(\\S+)`, 'i');
|
|
53
|
+
const m = src.match(re);
|
|
54
|
+
if (!m) return { text: src.trim(), value: '' };
|
|
55
|
+
const value = String(m[1] || '').trim();
|
|
56
|
+
const text = src.replace(m[0], ' ').replace(/\s+/g, ' ').trim();
|
|
57
|
+
return { text, value };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseTeamTaskArgs(raw) {
|
|
61
|
+
const src = String(raw || '').trim();
|
|
62
|
+
const first = src.match(/^(\S+)\s+([\s\S]+)$/);
|
|
63
|
+
if (!first) return null;
|
|
64
|
+
const targetName = first[1];
|
|
65
|
+
let rest = first[2].trim();
|
|
66
|
+
const scopePop = popFlag(rest, 'scope');
|
|
67
|
+
rest = scopePop.text;
|
|
68
|
+
const parentPop = popFlag(rest, 'parent');
|
|
69
|
+
rest = parentPop.text;
|
|
70
|
+
return {
|
|
71
|
+
targetName,
|
|
72
|
+
goal: rest,
|
|
73
|
+
scopeId: scopePop.value || '',
|
|
74
|
+
parentTaskId: parentPop.value || '',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function formatTaskSchedule(task) {
|
|
79
|
+
const at = typeof task.at === 'string' ? task.at.trim() : '';
|
|
80
|
+
if (at) {
|
|
81
|
+
const rawDays = task.days !== undefined ? task.days : task.weekdays;
|
|
82
|
+
let daysLabel = '';
|
|
83
|
+
if (Array.isArray(rawDays)) daysLabel = rawDays.join(',');
|
|
84
|
+
else if (typeof rawDays === 'string') daysLabel = rawDays.trim();
|
|
85
|
+
return daysLabel ? `at ${at} ${daysLabel}` : `at ${at}`;
|
|
86
|
+
}
|
|
87
|
+
if (task.interval) return `every ${task.interval}`;
|
|
88
|
+
return 'unspecified';
|
|
89
|
+
}
|
|
90
|
+
|
|
23
91
|
async function handleAdminCommand(ctx) {
|
|
24
92
|
const { bot, chatId, text } = ctx;
|
|
25
93
|
const state = ctx.state || {};
|
|
@@ -138,7 +206,7 @@ function createAdminCommandHandler(deps) {
|
|
|
138
206
|
msg += 'ð General:\n';
|
|
139
207
|
for (const t of general) {
|
|
140
208
|
const ts = state.tasks[t.name] || {};
|
|
141
|
-
msg += `${t.enabled !== false ? 'â
' : 'âž'} ${t.name} (${t
|
|
209
|
+
msg += `${t.enabled !== false ? 'â
' : 'âž'} ${t.name} (${formatTaskSchedule(t)}) ${ts.status || 'never_run'}\n`;
|
|
142
210
|
}
|
|
143
211
|
}
|
|
144
212
|
// Project tasks grouped by _project
|
|
@@ -152,7 +220,7 @@ function createAdminCommandHandler(deps) {
|
|
|
152
220
|
msg += `\n${proj.icon} ${proj.name}:\n`;
|
|
153
221
|
for (const t of tasks) {
|
|
154
222
|
const ts = state.tasks[t.name] || {};
|
|
155
|
-
msg += `${t.enabled !== false ? 'â
' : 'âž'} ${t.name} (${t
|
|
223
|
+
msg += `${t.enabled !== false ? 'â
' : 'âž'} ${t.name} (${formatTaskSchedule(t)}) ${ts.status || 'never_run'}\n`;
|
|
156
224
|
}
|
|
157
225
|
}
|
|
158
226
|
if (!msg) {
|
|
@@ -163,6 +231,216 @@ function createAdminCommandHandler(deps) {
|
|
|
163
231
|
return { handled: true, config };
|
|
164
232
|
}
|
|
165
233
|
|
|
234
|
+
// /TeamTask â create/list/detail/resume team collaboration tasks
|
|
235
|
+
const teamTaskCmdMatch = text.match(/^\/teamtask(?:\s+([\s\S]+))?$/i);
|
|
236
|
+
if (teamTaskCmdMatch) {
|
|
237
|
+
const args = String(teamTaskCmdMatch[1] || '').trim();
|
|
238
|
+
if (/^create$/i.test(args)) {
|
|
239
|
+
await bot.sendMessage(chatId, 'â çšæ³: /TeamTask create <agent> <ç®æ > [--scope <scopeId>] [--parent <taskId>]');
|
|
240
|
+
return { handled: true, config };
|
|
241
|
+
}
|
|
242
|
+
const createMatch = args.match(/^create\s+([\s\S]+)$/i);
|
|
243
|
+
if (createMatch) {
|
|
244
|
+
if (!taskEnvelope) {
|
|
245
|
+
await bot.sendMessage(chatId, 'â task protocol äžå¯çš');
|
|
246
|
+
return { handled: true, config };
|
|
247
|
+
}
|
|
248
|
+
const parsed = parseTeamTaskArgs(createMatch[1]);
|
|
249
|
+
if (!parsed || !parsed.targetName || !parsed.goal) {
|
|
250
|
+
await bot.sendMessage(chatId, 'â çšæ³: /TeamTask create <agent> <ç®æ > [--scope <scopeId>] [--parent <taskId>]');
|
|
251
|
+
return { handled: true, config };
|
|
252
|
+
}
|
|
253
|
+
const { targetName, goal, scopeId, parentTaskId } = parsed;
|
|
254
|
+
const targetKey = resolveProjectKey(targetName, config.projects || {});
|
|
255
|
+
if (!targetKey) {
|
|
256
|
+
await bot.sendMessage(chatId, `æªæŸå° agent: ${targetName}\nå¯çš: ${Object.keys(config.projects || {}).join(', ')}`);
|
|
257
|
+
return { handled: true, config };
|
|
258
|
+
}
|
|
259
|
+
const senderKey = resolveSenderKey(chatId, config);
|
|
260
|
+
const participants = (scopeId && taskBoard && taskBoard.listScopeParticipants)
|
|
261
|
+
? taskBoard.listScopeParticipants(scopeId)
|
|
262
|
+
: [];
|
|
263
|
+
participants.push(senderKey, targetKey);
|
|
264
|
+
const envelope = taskEnvelope.normalizeTaskEnvelope({
|
|
265
|
+
from_agent: senderKey,
|
|
266
|
+
to_agent: targetKey,
|
|
267
|
+
scope_id: scopeId || '',
|
|
268
|
+
parent_task_id: parentTaskId || null,
|
|
269
|
+
participants,
|
|
270
|
+
goal,
|
|
271
|
+
task_kind: 'team',
|
|
272
|
+
definition_of_done: [
|
|
273
|
+
'èŸåºå¯æ§è¡ç»æåå
³é®ç»è®º',
|
|
274
|
+
'å¿
èŠæ¶ç»åºäº§ç©è·¯åŸäžäžäžæ¥å»ºè®®',
|
|
275
|
+
],
|
|
276
|
+
inputs: {
|
|
277
|
+
source_chat_id: String(chatId),
|
|
278
|
+
source: 'mobile_teamtask',
|
|
279
|
+
},
|
|
280
|
+
priority: 'normal',
|
|
281
|
+
status: 'queued',
|
|
282
|
+
});
|
|
283
|
+
const checked = taskEnvelope.validateTaskEnvelope(envelope);
|
|
284
|
+
if (!checked.ok) {
|
|
285
|
+
await bot.sendMessage(chatId, `â TeamTask æ æ: ${checked.error}`);
|
|
286
|
+
return { handled: true, config };
|
|
287
|
+
}
|
|
288
|
+
const result = dispatchTask(targetKey, {
|
|
289
|
+
from: senderKey,
|
|
290
|
+
type: 'task',
|
|
291
|
+
priority: envelope.priority,
|
|
292
|
+
payload: {
|
|
293
|
+
title: goal.slice(0, 60),
|
|
294
|
+
prompt: goal,
|
|
295
|
+
task_envelope: envelope,
|
|
296
|
+
},
|
|
297
|
+
callback: false,
|
|
298
|
+
}, config);
|
|
299
|
+
if (result.success) {
|
|
300
|
+
await bot.sendMessage(chatId, [
|
|
301
|
+
`â
å·²å建 TeamTask 并掟å: ${envelope.task_id}`,
|
|
302
|
+
`Scope: ${envelope.scope_id || envelope.task_id}`,
|
|
303
|
+
`æ¥ç: /TeamTask ${envelope.task_id}`,
|
|
304
|
+
].join('\n'));
|
|
305
|
+
} else {
|
|
306
|
+
await bot.sendMessage(chatId, `â å建 TeamTask 倱莥: ${result.error}`);
|
|
307
|
+
}
|
|
308
|
+
return { handled: true, config };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (!taskBoard) {
|
|
312
|
+
await bot.sendMessage(chatId, 'â Task Board äžå¯çš');
|
|
313
|
+
return { handled: true, config };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (!args || /^list$/i.test(args)) {
|
|
317
|
+
const recent = taskBoard.listRecentTasks(10, null, 'team');
|
|
318
|
+
if (recent.length === 0) {
|
|
319
|
+
await bot.sendMessage(chatId, 'ææ TeamTaskã\näœ¿çš /TeamTask create <agent> <goal> å建ã');
|
|
320
|
+
return { handled: true, config };
|
|
321
|
+
}
|
|
322
|
+
let msg = 'ð§© TeamTask (æè¿10æ¡)\n';
|
|
323
|
+
for (const t of recent) {
|
|
324
|
+
msg += `\n- ${t.task_id} [${t.status}] scope=${t.scope_id || t.task_id}\n ${t.from_agent}â${t.to_agent} · ${t.goal.slice(0, 80)}`;
|
|
325
|
+
}
|
|
326
|
+
msg += '\n\næ¥ç诊æ
: /TeamTask <task_id>\nç»è·: /TeamTask resume <task_id>';
|
|
327
|
+
await bot.sendMessage(chatId, msg);
|
|
328
|
+
return { handled: true, config };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const resumeMatch = args.match(/^resume\s+(\S+)$/i);
|
|
332
|
+
if (resumeMatch) {
|
|
333
|
+
const taskId = resumeMatch[1];
|
|
334
|
+
const task = taskBoard.getTask(taskId);
|
|
335
|
+
if (!task || task.task_kind !== 'team') {
|
|
336
|
+
await bot.sendMessage(chatId, `â æªæŸå° TeamTask: ${taskId}`);
|
|
337
|
+
return { handled: true, config };
|
|
338
|
+
}
|
|
339
|
+
const targetKey = task.to_agent;
|
|
340
|
+
if (!config.projects || !config.projects[targetKey]) {
|
|
341
|
+
await bot.sendMessage(chatId, `â ç®æ agent äžååš: ${targetKey}`);
|
|
342
|
+
return { handled: true, config };
|
|
343
|
+
}
|
|
344
|
+
const envelope = taskEnvelope && taskEnvelope.normalizeTaskEnvelope
|
|
345
|
+
? taskEnvelope.normalizeTaskEnvelope({
|
|
346
|
+
...task,
|
|
347
|
+
status: 'queued',
|
|
348
|
+
updated_at: new Date().toISOString(),
|
|
349
|
+
task_kind: 'team',
|
|
350
|
+
participants: taskBoard.listScopeParticipants(task.scope_id || task.task_id),
|
|
351
|
+
}, {
|
|
352
|
+
from_agent: task.from_agent || resolveSenderKey(chatId, config),
|
|
353
|
+
to_agent: targetKey,
|
|
354
|
+
scope_id: task.scope_id || task.task_id,
|
|
355
|
+
})
|
|
356
|
+
: {
|
|
357
|
+
task_id: task.task_id,
|
|
358
|
+
scope_id: task.scope_id || task.task_id,
|
|
359
|
+
from_agent: task.from_agent || resolveSenderKey(chatId, config),
|
|
360
|
+
to_agent: targetKey,
|
|
361
|
+
participants: taskBoard.listScopeParticipants(task.scope_id || task.task_id),
|
|
362
|
+
goal: task.goal,
|
|
363
|
+
definition_of_done: task.definition_of_done || [],
|
|
364
|
+
inputs: task.inputs || {},
|
|
365
|
+
artifacts: task.artifacts || [],
|
|
366
|
+
owned_paths: task.owned_paths || [],
|
|
367
|
+
priority: task.priority || 'normal',
|
|
368
|
+
status: 'queued',
|
|
369
|
+
task_kind: 'team',
|
|
370
|
+
created_at: task.created_at,
|
|
371
|
+
updated_at: new Date().toISOString(),
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const result = dispatchTask(targetKey, {
|
|
375
|
+
from: envelope.from_agent || 'user',
|
|
376
|
+
type: 'task',
|
|
377
|
+
priority: envelope.priority || 'normal',
|
|
378
|
+
payload: {
|
|
379
|
+
title: envelope.goal.slice(0, 60),
|
|
380
|
+
prompt: envelope.goal,
|
|
381
|
+
task_envelope: envelope,
|
|
382
|
+
},
|
|
383
|
+
callback: false,
|
|
384
|
+
new_session: false,
|
|
385
|
+
}, config);
|
|
386
|
+
|
|
387
|
+
if (result.success) {
|
|
388
|
+
taskBoard.appendTaskEvent(task.task_id, 'task_resume_requested', String(chatId), { by: String(chatId) });
|
|
389
|
+
await bot.sendMessage(chatId, `â
å·²ç»è· TeamTask: ${task.task_id}`);
|
|
390
|
+
} else {
|
|
391
|
+
await bot.sendMessage(chatId, `â ç»è·å€±èŽ¥: ${result.error}`);
|
|
392
|
+
}
|
|
393
|
+
return { handled: true, config };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (/^resume$/i.test(args)) {
|
|
397
|
+
await bot.sendMessage(chatId, 'â çšæ³: /TeamTask resume <task_id>');
|
|
398
|
+
return { handled: true, config };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const task = taskBoard.getTask(args);
|
|
402
|
+
if (!task || task.task_kind !== 'team') {
|
|
403
|
+
await bot.sendMessage(chatId, `â æªæŸå° TeamTask: ${args}`);
|
|
404
|
+
return { handled: true, config };
|
|
405
|
+
}
|
|
406
|
+
const events = taskBoard.listTaskEvents(task.task_id, 8);
|
|
407
|
+
const scopeId = task.scope_id || task.task_id;
|
|
408
|
+
const scopeTasks = taskBoard.listScopeTasks(scopeId, 12);
|
|
409
|
+
const scopeParticipants = taskBoard.listScopeParticipants(scopeId);
|
|
410
|
+
let detail = [
|
|
411
|
+
`ð§© TeamTask: ${task.task_id}`,
|
|
412
|
+
`Scope: ${scopeId}`,
|
|
413
|
+
`ç¶æ: ${task.status}`,
|
|
414
|
+
`äŒå
级: ${task.priority}`,
|
|
415
|
+
`æµå: ${task.from_agent} â ${task.to_agent}`,
|
|
416
|
+
`ç®æ : ${task.goal}`,
|
|
417
|
+
];
|
|
418
|
+
if (scopeParticipants.length > 0) {
|
|
419
|
+
detail.push(`åäžè
: ${scopeParticipants.join(', ')}`);
|
|
420
|
+
}
|
|
421
|
+
if (Array.isArray(task.definition_of_done) && task.definition_of_done.length > 0) {
|
|
422
|
+
detail.push('DoD:');
|
|
423
|
+
for (const d of task.definition_of_done.slice(0, 6)) detail.push(`- ${d}`);
|
|
424
|
+
}
|
|
425
|
+
if (Array.isArray(task.artifacts) && task.artifacts.length > 0) {
|
|
426
|
+
detail.push('产ç©:');
|
|
427
|
+
for (const a of task.artifacts.slice(0, 6)) detail.push(`- ${a}`);
|
|
428
|
+
}
|
|
429
|
+
if (task.last_error) detail.push(`é误: ${task.last_error.slice(0, 180)}`);
|
|
430
|
+
if (events.length > 0) {
|
|
431
|
+
detail.push('æè¿äºä»¶:');
|
|
432
|
+
for (const ev of events.slice(0, 5)) detail.push(`- [${ev.event_type}] ${ev.actor} @ ${ev.created_at}`);
|
|
433
|
+
}
|
|
434
|
+
if (scopeTasks.length > 1) {
|
|
435
|
+
detail.push('å Scope çžå
³ä»»å¡:');
|
|
436
|
+
for (const st of scopeTasks.filter(x => x.task_id !== task.task_id).slice(0, 5)) {
|
|
437
|
+
detail.push(`- ${st.task_id} [${st.status}] ${st.from_agent}â${st.to_agent}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
await bot.sendMessage(chatId, detail.join('\n'));
|
|
441
|
+
return { handled: true, config };
|
|
442
|
+
}
|
|
443
|
+
|
|
166
444
|
// /dispatch â inter-agent task dispatch
|
|
167
445
|
if (text.startsWith('/dispatch')) {
|
|
168
446
|
const args = text.slice('/dispatch'.length).trim();
|
|
@@ -216,21 +494,14 @@ function createAdminCommandHandler(deps) {
|
|
|
216
494
|
const prompt = toMatch[2].trim();
|
|
217
495
|
|
|
218
496
|
// Resolve target by project key or nickname
|
|
219
|
-
|
|
220
|
-
for (const [key, proj] of Object.entries(config.projects || {})) {
|
|
221
|
-
if (key === targetName || (proj.nicknames || []).some(n => n === targetName)) {
|
|
222
|
-
targetKey = key;
|
|
223
|
-
break;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
497
|
+
const targetKey = resolveProjectKey(targetName, config.projects || {});
|
|
226
498
|
if (!targetKey) {
|
|
227
499
|
await bot.sendMessage(chatId, `æªæŸå° agent: ${targetName}\nå¯çš: ${Object.keys(config.projects || {}).join(', ')}`);
|
|
228
500
|
return { handled: true, config };
|
|
229
501
|
}
|
|
230
502
|
|
|
231
503
|
// Determine sender from current chat's project mapping
|
|
232
|
-
const
|
|
233
|
-
const senderKey = chatAgentMap[chatId] || 'user';
|
|
504
|
+
const senderKey = resolveSenderKey(chatId, config);
|
|
234
505
|
|
|
235
506
|
const projInfo = config.projects[targetKey] || {};
|
|
236
507
|
// Find the target project's own Feishu chat (reverse lookup of chat_agent_map)
|
|
@@ -263,7 +534,14 @@ function createAdminCommandHandler(deps) {
|
|
|
263
534
|
return { handled: true, config };
|
|
264
535
|
}
|
|
265
536
|
|
|
266
|
-
await bot.sendMessage(chatId,
|
|
537
|
+
await bot.sendMessage(chatId, [
|
|
538
|
+
'çšæ³:',
|
|
539
|
+
'/dispatch status â æ¥çç¶æ',
|
|
540
|
+
'/dispatch log â æ¥çè®°åœ',
|
|
541
|
+
'/dispatch to <agent> <ä»»å¡å
容> â çŽæ¥è·š agent 掟å',
|
|
542
|
+
'/TeamTask create <agent> <ç®æ > [--scope <id>] [--parent <id>] â å建/ç»æ¥ TeamTask',
|
|
543
|
+
'/TeamTask â æ¥ç TeamTask å衚',
|
|
544
|
+
].join('\n'));
|
|
267
545
|
return { handled: true, config };
|
|
268
546
|
}
|
|
269
547
|
|
|
@@ -274,6 +552,66 @@ function createAdminCommandHandler(deps) {
|
|
|
274
552
|
return { handled: true, config };
|
|
275
553
|
}
|
|
276
554
|
|
|
555
|
+
if (text === '/usage' || text.startsWith('/usage ')) {
|
|
556
|
+
const arg = text.slice('/usage'.length).trim() || 'today';
|
|
557
|
+
const usage = state.usage || {};
|
|
558
|
+
const daily = usage.daily || {};
|
|
559
|
+
const categories = usage.categories || {};
|
|
560
|
+
const limit = (config.budget && config.budget.daily_limit) || 50000;
|
|
561
|
+
const todayIso = new Date().toISOString().slice(0, 10);
|
|
562
|
+
|
|
563
|
+
// Resolve date range
|
|
564
|
+
let days = 1;
|
|
565
|
+
if (arg === 'week') days = 7;
|
|
566
|
+
else if (arg === 'month') days = 30;
|
|
567
|
+
else if (/^\d+d$/.test(arg)) days = Math.min(90, parseInt(arg, 10));
|
|
568
|
+
|
|
569
|
+
const dates = [];
|
|
570
|
+
for (let i = days - 1; i >= 0; i--) {
|
|
571
|
+
const d = new Date(`${todayIso}T00:00:00.000Z`);
|
|
572
|
+
d.setUTCDate(d.getUTCDate() - i);
|
|
573
|
+
dates.push(d.toISOString().slice(0, 10));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Aggregate tokens by category across the date window
|
|
577
|
+
const totals = {};
|
|
578
|
+
let grandTotal = 0;
|
|
579
|
+
for (const date of dates) {
|
|
580
|
+
const bucket = daily[date] || {};
|
|
581
|
+
for (const [key, val] of Object.entries(bucket)) {
|
|
582
|
+
if (key === 'total') continue;
|
|
583
|
+
const n = Math.max(0, Math.floor(Number(val) || 0));
|
|
584
|
+
totals[key] = (totals[key] || 0) + n;
|
|
585
|
+
grandTotal += n;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// Fallback: if no daily breakdown yet, use categories totals for today
|
|
589
|
+
if (grandTotal === 0 && days === 1) {
|
|
590
|
+
for (const [key, meta] of Object.entries(categories)) {
|
|
591
|
+
const n = Math.max(0, Math.floor(Number(meta && meta.total) || 0));
|
|
592
|
+
if (n > 0) { totals[key] = n; grandTotal += n; }
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const label = days === 1 ? `仿¥ (${todayIso})` : `è¿ ${days} 倩`;
|
|
597
|
+
const budgetPct = limit > 0 ? ((grandTotal / limit) * 100).toFixed(1) : 'â';
|
|
598
|
+
let lines = [`ð Token çšé â ${label}`, `å计: ${grandTotal.toLocaleString()} / ${limit.toLocaleString()} tokens (${budgetPct}%)`];
|
|
599
|
+
|
|
600
|
+
// Render by canonical order, then extras
|
|
601
|
+
const orderedKeys = [...USAGE_CATEGORY_ORDER, ...Object.keys(totals).filter(k => !USAGE_CATEGORY_ORDER.includes(k))];
|
|
602
|
+
for (const key of orderedKeys) {
|
|
603
|
+
const n = totals[key] || 0;
|
|
604
|
+
if (n === 0 && !CORE_USAGE_CATEGORIES.includes(key)) continue;
|
|
605
|
+
const pct = grandTotal > 0 ? ((n / grandTotal) * 100).toFixed(1) : '0.0';
|
|
606
|
+
const lbl = USAGE_CATEGORY_LABEL[key] || key;
|
|
607
|
+
const bar = 'â'.repeat(Math.round(Number(pct) / 10)).padEnd(10, 'â');
|
|
608
|
+
lines.push(`${lbl}: ${n.toLocaleString()} tokens (${pct}%) ${bar}`);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
await bot.sendMessage(chatId, lines.join('\n'));
|
|
612
|
+
return { handled: true, config };
|
|
613
|
+
}
|
|
614
|
+
|
|
277
615
|
if (text === '/quiet') {
|
|
278
616
|
try {
|
|
279
617
|
const doc = yaml.load(fs.readFileSync(BRAIN_FILE, 'utf8')) || {};
|