zozul-cli 0.3.4 → 0.3.5

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.
@@ -1043,64 +1043,10 @@ function renderSummaryStats(s) {
1043
1043
 
1044
1044
  async function loadTasks() {
1045
1045
  try {
1046
- const [stats, tasks] = await Promise.all([
1047
- fetchJson('/api/stats'),
1048
- fetchJson('/api/tasks'),
1049
- ]);
1050
- if (!tasks.length) { allTaskGroups = []; renderTaskTable([]); return; }
1051
-
1052
- // Discover tag combinations by sampling one turn per tag
1053
- const samples = await Promise.all(
1054
- tasks.map(t => fetchJson('/api/tasks/turns?tags=' + encodeURIComponent(t.task) + '&mode=any&limit=1')
1055
- .then(turns => turns[0]?.tags || t.task)
1056
- .catch(() => t.task)
1057
- )
1058
- );
1059
- // Deduplicate: normalize each combo to sorted pipe-separated
1060
- const combos = [...new Set(samples.map(s => s.split(', ').sort().join('|')))];
1061
-
1062
- // Fetch stats for each unique combo
1063
- const comboStats = await Promise.all(
1064
- combos.map(combo => {
1065
- const tags = combo.split('|');
1066
- const qs = 'tags=' + tags.map(encodeURIComponent).join(',') + '&mode=all';
1067
- return fetchJson('/api/tasks/stats?' + qs)
1068
- .then(s => ({ ...s, tags: combo }))
1069
- .catch(() => ({ tags: combo, total_turns: 0, user_turns: 0, total_duration_ms: 0, total_cost_usd: 0 }));
1070
- })
1071
- );
1072
-
1073
- let taskGroups = comboStats.map(s => ({
1074
- tags: s.tags,
1075
- turn_count: s.total_turns ?? 0,
1076
- human_interventions: s.user_turns ?? 0,
1077
- total_duration_ms: s.total_duration_ms ?? 0,
1078
- total_cost_usd: s.total_cost_usd ?? 0,
1079
- last_seen: s.last_seen ?? null,
1080
- }));
1081
-
1082
- // Add cost gap to the Untagged row so task costs sum to total
1083
- const attributedCost = taskGroups.reduce((s, g) => s + (g.total_cost_usd || 0), 0);
1084
- const totalCost = stats.total_cost_usd || 0;
1085
- const gap = totalCost - attributedCost;
1086
- if (gap > 0.01) {
1087
- const untagged = taskGroups.find(g => g.tags === 'Untagged');
1088
- if (untagged) {
1089
- untagged.total_cost_usd = (untagged.total_cost_usd || 0) + gap;
1090
- } else {
1091
- taskGroups.push({
1092
- tags: 'Untagged',
1093
- turn_count: 0,
1094
- human_interventions: 0,
1095
- total_duration_ms: 0,
1096
- total_cost_usd: gap,
1097
- last_seen: null,
1098
- });
1099
- }
1100
- }
1101
-
1102
- allTaskGroups = taskGroups;
1103
- renderTaskTable(sortData(taskGroups, sortState.tasks));
1046
+ const timeQs = timeQueryString();
1047
+ const groups = await fetchJson('/api/task-groups' + (timeQs ? '?' + timeQs : ''));
1048
+ allTaskGroups = groups;
1049
+ renderTaskTable(sortData(groups, sortState.tasks));
1104
1050
  } catch (e) {
1105
1051
  console.error('tasks load failed', e);
1106
1052
  }
@@ -1152,8 +1098,17 @@ async function showTaskDetail(tagSet) {
1152
1098
  statsEl.innerHTML = '<div class="stat-card" style="grid-column:1/-1"><div class="label">Loading...</div></div>';
1153
1099
 
1154
1100
  if (tags.length === 1 && tags[0] === 'Untagged') {
1155
- statsEl.innerHTML = '<div class="stat-card" style="grid-column:1/-1"><div class="label">Task</div><div class="value" style="font-size:16px">' + pills + '</div></div>';
1156
- document.getElementById('task-turns-table').innerHTML = '<tr><td colspan="5" class="empty">Untagged turns cannot be drilled into.</td></tr>';
1101
+ const group = allTaskGroups.find(g => g.tags === 'Untagged') || {};
1102
+ statsEl.innerHTML = [
1103
+ { label: 'Task', value: '<span style="font-size:14px">' + pills + '</span>' },
1104
+ { label: 'Total Turns', value: fmtNum(group.turn_count) },
1105
+ { label: 'Human Interventions', value: fmtNum(group.human_interventions) },
1106
+ { label: 'Duration', value: fmtDuration(group.total_duration_ms) },
1107
+ { label: 'Cost', value: fmtCost(group.total_cost_usd) },
1108
+ ].map(c =>
1109
+ '<div class="stat-card"><div class="label">' + c.label + '</div><div class="value" style="font-size:16px">' + c.value + '</div></div>'
1110
+ ).join('');
1111
+ document.getElementById('task-turns-table').innerHTML = '';
1157
1112
  document.getElementById('task-turns-pagination').innerHTML = '';
1158
1113
  return;
1159
1114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zozul-cli",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Observability for Claude Code — track token usage, costs, turns, and conversation history",
5
5
  "readmeFilename": "README.md",
6
6
  "type": "module",
@@ -1043,64 +1043,10 @@ function renderSummaryStats(s) {
1043
1043
 
1044
1044
  async function loadTasks() {
1045
1045
  try {
1046
- const [stats, tasks] = await Promise.all([
1047
- fetchJson('/api/stats'),
1048
- fetchJson('/api/tasks'),
1049
- ]);
1050
- if (!tasks.length) { allTaskGroups = []; renderTaskTable([]); return; }
1051
-
1052
- // Discover tag combinations by sampling one turn per tag
1053
- const samples = await Promise.all(
1054
- tasks.map(t => fetchJson('/api/tasks/turns?tags=' + encodeURIComponent(t.task) + '&mode=any&limit=1')
1055
- .then(turns => turns[0]?.tags || t.task)
1056
- .catch(() => t.task)
1057
- )
1058
- );
1059
- // Deduplicate: normalize each combo to sorted pipe-separated
1060
- const combos = [...new Set(samples.map(s => s.split(', ').sort().join('|')))];
1061
-
1062
- // Fetch stats for each unique combo
1063
- const comboStats = await Promise.all(
1064
- combos.map(combo => {
1065
- const tags = combo.split('|');
1066
- const qs = 'tags=' + tags.map(encodeURIComponent).join(',') + '&mode=all';
1067
- return fetchJson('/api/tasks/stats?' + qs)
1068
- .then(s => ({ ...s, tags: combo }))
1069
- .catch(() => ({ tags: combo, total_turns: 0, user_turns: 0, total_duration_ms: 0, total_cost_usd: 0 }));
1070
- })
1071
- );
1072
-
1073
- let taskGroups = comboStats.map(s => ({
1074
- tags: s.tags,
1075
- turn_count: s.total_turns ?? 0,
1076
- human_interventions: s.user_turns ?? 0,
1077
- total_duration_ms: s.total_duration_ms ?? 0,
1078
- total_cost_usd: s.total_cost_usd ?? 0,
1079
- last_seen: s.last_seen ?? null,
1080
- }));
1081
-
1082
- // Add cost gap to the Untagged row so task costs sum to total
1083
- const attributedCost = taskGroups.reduce((s, g) => s + (g.total_cost_usd || 0), 0);
1084
- const totalCost = stats.total_cost_usd || 0;
1085
- const gap = totalCost - attributedCost;
1086
- if (gap > 0.01) {
1087
- const untagged = taskGroups.find(g => g.tags === 'Untagged');
1088
- if (untagged) {
1089
- untagged.total_cost_usd = (untagged.total_cost_usd || 0) + gap;
1090
- } else {
1091
- taskGroups.push({
1092
- tags: 'Untagged',
1093
- turn_count: 0,
1094
- human_interventions: 0,
1095
- total_duration_ms: 0,
1096
- total_cost_usd: gap,
1097
- last_seen: null,
1098
- });
1099
- }
1100
- }
1101
-
1102
- allTaskGroups = taskGroups;
1103
- renderTaskTable(sortData(taskGroups, sortState.tasks));
1046
+ const timeQs = timeQueryString();
1047
+ const groups = await fetchJson('/api/task-groups' + (timeQs ? '?' + timeQs : ''));
1048
+ allTaskGroups = groups;
1049
+ renderTaskTable(sortData(groups, sortState.tasks));
1104
1050
  } catch (e) {
1105
1051
  console.error('tasks load failed', e);
1106
1052
  }
@@ -1152,8 +1098,17 @@ async function showTaskDetail(tagSet) {
1152
1098
  statsEl.innerHTML = '<div class="stat-card" style="grid-column:1/-1"><div class="label">Loading...</div></div>';
1153
1099
 
1154
1100
  if (tags.length === 1 && tags[0] === 'Untagged') {
1155
- statsEl.innerHTML = '<div class="stat-card" style="grid-column:1/-1"><div class="label">Task</div><div class="value" style="font-size:16px">' + pills + '</div></div>';
1156
- document.getElementById('task-turns-table').innerHTML = '<tr><td colspan="5" class="empty">Untagged turns cannot be drilled into.</td></tr>';
1101
+ const group = allTaskGroups.find(g => g.tags === 'Untagged') || {};
1102
+ statsEl.innerHTML = [
1103
+ { label: 'Task', value: '<span style="font-size:14px">' + pills + '</span>' },
1104
+ { label: 'Total Turns', value: fmtNum(group.turn_count) },
1105
+ { label: 'Human Interventions', value: fmtNum(group.human_interventions) },
1106
+ { label: 'Duration', value: fmtDuration(group.total_duration_ms) },
1107
+ { label: 'Cost', value: fmtCost(group.total_cost_usd) },
1108
+ ].map(c =>
1109
+ '<div class="stat-card"><div class="label">' + c.label + '</div><div class="value" style="font-size:16px">' + c.value + '</div></div>'
1110
+ ).join('');
1111
+ document.getElementById('task-turns-table').innerHTML = '';
1157
1112
  document.getElementById('task-turns-pagination').innerHTML = '';
1158
1113
  return;
1159
1114
  }