@jjlabsio/claude-crew 0.1.14 → 0.1.15

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.
@@ -11,7 +11,7 @@
11
11
  "name": "claude-crew",
12
12
  "source": "./",
13
13
  "description": "오케스트레이터 + PM, 플래너, 개발, QA, 마케팅 에이전트 팀으로 단일 제품의 개발과 마케팅을 통합 관리",
14
- "version": "0.1.14",
14
+ "version": "0.1.15",
15
15
  "author": {
16
16
  "name": "Jaejin Song",
17
17
  "email": "wowlxx28@gmail.com"
@@ -28,5 +28,5 @@
28
28
  "category": "workflow"
29
29
  }
30
30
  ],
31
- "version": "0.1.14"
31
+ "version": "0.1.15"
32
32
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-crew",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "1인 SaaS 개발자를 위한 멀티 에이전트 오케스트레이션 — 개발, 마케팅, 일정을 한 대화에서 통합 관리",
5
5
  "author": {
6
6
  "name": "Jaejin Song",
package/hud/index.mjs CHANGED
@@ -187,6 +187,35 @@ function colorizeContext(pct) {
187
187
  return `ctx:${color(`${pct}%`)}`;
188
188
  }
189
189
 
190
+ // ---------------------------------------------------------------------------
191
+ // Rate limits (5h / weekly)
192
+ // ---------------------------------------------------------------------------
193
+ function getRateLimits(stdin) {
194
+ const rl = stdin?.rate_limits;
195
+ if (!rl) return null;
196
+ const parse = (v) => {
197
+ if (v == null) return null;
198
+ const n = typeof v === 'number' ? v : parseFloat(v);
199
+ return isNaN(n) ? null : Math.round(Math.min(Math.max(n, 0), 100));
200
+ };
201
+ const fiveHour = parse(rl.five_hour?.used_percentage);
202
+ const sevenDay = parse(rl.seven_day?.used_percentage);
203
+ if (fiveHour == null && sevenDay == null) return null;
204
+ return { fiveHour, sevenDay };
205
+ }
206
+
207
+ function colorizeRateLimits(limits) {
208
+ if (!limits) return null;
209
+ const colorize = (pct) => {
210
+ const color = pct >= 85 ? red : pct >= 70 ? yellow : green;
211
+ return color(`${pct}%`);
212
+ };
213
+ const parts = [];
214
+ if (limits.fiveHour != null) parts.push(`5h:${colorize(limits.fiveHour)}`);
215
+ if (limits.sevenDay != null) parts.push(`weekly:${colorize(limits.sevenDay)}`);
216
+ return parts.join(' ');
217
+ }
218
+
190
219
  // ---------------------------------------------------------------------------
191
220
  // Transcript parsing (agents + skills)
192
221
  // ---------------------------------------------------------------------------
@@ -261,7 +290,10 @@ function parseTranscript(transcriptPath) {
261
290
  if (entry.type === 'tool_result') {
262
291
  const toolUseId = entry.tool_use_id;
263
292
  if (toolUseId && agentMap.has(toolUseId)) {
264
- agentMap.get(toolUseId).status = 'completed';
293
+ const agent = agentMap.get(toolUseId);
294
+ agent.status = 'completed';
295
+ const ts = entry.timestamp || lastTimestamp;
296
+ if (ts) agent.endTime = new Date(ts);
265
297
  }
266
298
  }
267
299
  if (entry.type === 'user') {
@@ -271,8 +303,10 @@ function parseTranscript(transcriptPath) {
271
303
  if (block.type === 'tool_result') {
272
304
  const toolUseId = block.tool_use_id;
273
305
  if (toolUseId && agentMap.has(toolUseId)) {
274
- agentMap.get(toolUseId).status = 'completed';
275
- }
306
+ const agent = agentMap.get(toolUseId);
307
+ agent.status = 'completed';
308
+ const ts = entry.timestamp || lastTimestamp;
309
+ if (ts) agent.endTime = new Date(ts);
276
310
  }
277
311
  }
278
312
  }
@@ -307,14 +341,18 @@ function shortModelName(model) {
307
341
  // ---------------------------------------------------------------------------
308
342
  // Agent duration formatting
309
343
  // ---------------------------------------------------------------------------
310
- function formatAgentDuration(startTime) {
311
- if (!startTime) return '?';
312
- const ms = Date.now() - startTime.getTime();
344
+ function formatAgentDuration(startTime, endTime) {
345
+ if (!startTime) return '';
346
+ const ms = (endTime ?? new Date()).getTime() - startTime.getTime();
347
+ if (ms < 1000) return '<1s';
313
348
  const seconds = Math.floor(ms / 1000);
314
- const minutes = Math.floor(seconds / 60);
315
- if (seconds < 10) return '';
316
349
  if (seconds < 60) return `${seconds}s`;
317
- return `${minutes}m`;
350
+ const minutes = Math.floor(seconds / 60);
351
+ const secs = seconds % 60;
352
+ if (minutes < 60) return `${minutes}m${secs}s`;
353
+ const hours = Math.floor(minutes / 60);
354
+ const mins = minutes % 60;
355
+ return `${hours}h${mins}m`;
318
356
  }
319
357
 
320
358
  // ---------------------------------------------------------------------------
@@ -337,7 +375,7 @@ function renderAgentsMultiLine(agents, maxLines = 5) {
337
375
  const rawType = a.type.includes(':') ? a.type.split(':').pop() : a.type;
338
376
  const name = rawType.padEnd(7);
339
377
  const model = shortModelName(a.model).padEnd(8);
340
- const duration = formatAgentDuration(a.startTime).padStart(4);
378
+ const duration = formatAgentDuration(a.startTime, a.endTime).padStart(6);
341
379
  const desc = a.description.length > 40 ? a.description.slice(0, 37) + '...' : a.description;
342
380
 
343
381
  detailLines.push(
@@ -441,6 +479,10 @@ async function main() {
441
479
 
442
480
  midElements.push(colorizeSession(transcript.sessionStart));
443
481
 
482
+ const rateLimits = getRateLimits(stdin);
483
+ const rateLimitsStr = colorizeRateLimits(rateLimits);
484
+ if (rateLimitsStr) midElements.push(rateLimitsStr);
485
+
444
486
  // --- Output ---
445
487
  const outputLines = [];
446
488
  outputLines.push(topElements.join(SEPARATOR));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlabsio/claude-crew",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "1인 SaaS 개발자를 위한 멀티 에이전트 오케스트레이션 — 개발, 마케팅, 일정을 한 대화에서 통합 관리",
5
5
  "author": "Jaejin Song <wowlxx28@gmail.com>",
6
6
  "license": "MIT",