claude-task-worker 0.137.0 → 0.138.0

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 CHANGED
@@ -139,7 +139,7 @@ claude-task-worker <command> [--epic <issue-number>]... [--label <label>]... [--
139
139
  | `install` / `update` | 上記「セットアップ」を参照 |
140
140
  | `cloud-setup [--force]` | クラウド VM 側の準備(下記「`--cloud`」を参照) |
141
141
  | `list-workers` | プリセットとカスタムの全ワーカーを名前・種別・有効/無効・ロード元で表示(`workerFiles` のロード失敗時は exit 1) |
142
- | `usage` | Claude API 使用状況(5時間/7日間の利用率とリセット時刻)を表示し、Slack にも通知 |
142
+ | `usage` | Claude API 使用状況(5時間/7日間の利用率とリセット時刻)を表示し、Slack にも通知。以降は360秒(6分)ごとに取得し直して表示・RunCat 用スナップショットを更新し続ける(Ctrl-C で終了。Slack 通知は起動時のみ)。プランによって返らない窓(Ultra の5時間枠など)は省いて表示する |
143
143
  | `version` | CLI のバージョンを表示(`--version` / `-v` も可) |
144
144
 
145
145
  ### `--epic <issue-number>`
@@ -2167,6 +2167,7 @@ function ceilToMinute(date) {
2167
2167
  return remainder === 0 ? date : new Date(date.getTime() + (MINUTE_MS - remainder));
2168
2168
  }
2169
2169
  function parseResetsAt(isoString) {
2170
+ if (!isoString) return null;
2170
2171
  const date = new Date(isoString);
2171
2172
  return Number.isNaN(date.getTime()) ? null : ceilToMinute(date);
2172
2173
  }
@@ -2192,15 +2193,17 @@ function metric(title, pct, resetsAt, now) {
2192
2193
  };
2193
2194
  }
2194
2195
  function buildRuncatSnapshot(usage, now = /* @__PURE__ */ new Date()) {
2195
- const bar = `${formatG(usage.fiveHourUtilization)}/${formatG(usage.sevenDayUtilization)}`;
2196
- const stamp = resetHour(usage.fiveHourResetsAt);
2196
+ const windows = [
2197
+ ["5h", usage.fiveHourUtilization, usage.fiveHourResetsAt],
2198
+ ["7d", usage.sevenDayUtilization, usage.sevenDayResetsAt]
2199
+ ];
2200
+ const present = windows.filter((w) => w[1] !== null);
2201
+ const bar = present.map(([, pct]) => formatG(pct)).join("/");
2202
+ const stamp = present.length > 0 ? resetHour(present[0][2]) : "";
2197
2203
  return {
2198
2204
  title: "Claude Code",
2199
2205
  symbol: "staroflife",
2200
- metrics: [
2201
- metric("5h", usage.fiveHourUtilization, usage.fiveHourResetsAt, now),
2202
- metric("7d", usage.sevenDayUtilization, usage.sevenDayResetsAt, now)
2203
- ],
2206
+ metrics: present.map(([title, pct, resetsAt]) => metric(title, pct, resetsAt, now)),
2204
2207
  metricsBarValue: stamp ? `${bar} \u21BB${stamp}` : bar,
2205
2208
  lastUpdatedDate: now.toISOString().replace(/\.\d{3}Z$/, "Z")
2206
2209
  };
@@ -2214,7 +2217,10 @@ function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
2214
2217
  writeFileSync3(tmp, JSON.stringify(snapshot), "utf-8");
2215
2218
  renameSync2(tmp, outFile);
2216
2219
  } catch (err) {
2217
- rmSync(tmp, { force: true });
2220
+ try {
2221
+ rmSync(tmp, { force: true });
2222
+ } catch {
2223
+ }
2218
2224
  console.error(`[runcat] Failed to write ${outFile}: ${err}`);
2219
2225
  }
2220
2226
  }
@@ -2223,6 +2229,7 @@ function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
2223
2229
  var execAsync = promisify2(exec);
2224
2230
  var WEBHOOK_URL = process.env.CLAUDE_TASK_WORKER_SLACK_WEBHOOK_URL;
2225
2231
  var USAGE_CACHE_PATH = "/tmp/claude-usage-cache.json";
2232
+ var USAGE_POLL_INTERVAL_SECONDS = 360;
2226
2233
  var USAGE_CACHE_TTL_SECONDS = 360;
2227
2234
  async function send(payload) {
2228
2235
  if (!WEBHOOK_URL) return;
@@ -2236,6 +2243,14 @@ async function send(payload) {
2236
2243
  console.error(`[slack] Failed to send notification: ${err}`);
2237
2244
  }
2238
2245
  }
2246
+ function parseUsageResponse(body) {
2247
+ return {
2248
+ fiveHourUtilization: body.five_hour?.utilization ?? null,
2249
+ fiveHourResetsAt: body.five_hour?.resets_at ?? null,
2250
+ sevenDayUtilization: body.seven_day?.utilization ?? null,
2251
+ sevenDayResetsAt: body.seven_day?.resets_at ?? null
2252
+ };
2253
+ }
2239
2254
  function extractToken(credentials) {
2240
2255
  if (typeof credentials === "string") return credentials;
2241
2256
  return credentials.claudeAiOauth?.accessToken ?? credentials.oauth_token ?? credentials.access_token ?? "";
@@ -2266,8 +2281,8 @@ function writeUsageCache(data) {
2266
2281
  } catch {
2267
2282
  }
2268
2283
  }
2269
- async function fetchUsageInfo() {
2270
- const cached = readUsageCache();
2284
+ async function fetchUsageInfo(fresh) {
2285
+ const cached = fresh ? null : readUsageCache();
2271
2286
  if (cached) return cached;
2272
2287
  try {
2273
2288
  const token = await getOAuthToken();
@@ -2281,13 +2296,7 @@ async function fetchUsageInfo() {
2281
2296
  console.error(`[slack] Usage API returned ${res.status}`);
2282
2297
  return null;
2283
2298
  }
2284
- const body = await res.json();
2285
- const data = {
2286
- fiveHourUtilization: body.five_hour.utilization,
2287
- fiveHourResetsAt: body.five_hour.resets_at,
2288
- sevenDayUtilization: body.seven_day.utilization,
2289
- sevenDayResetsAt: body.seven_day.resets_at
2290
- };
2299
+ const data = parseUsageResponse(await res.json());
2291
2300
  writeUsageCache(data);
2292
2301
  return data;
2293
2302
  } catch (err) {
@@ -2310,17 +2319,23 @@ function formatResetTimeJST(isoString) {
2310
2319
  minute: "2-digit"
2311
2320
  });
2312
2321
  }
2322
+ function formatWindow(label, utilization, resetsAt) {
2323
+ if (utilization === null) return null;
2324
+ const reset = resetsAt ? ` (reset: ${formatResetTimeJST(resetsAt)})` : "";
2325
+ return `${label}: ${utilization.toFixed(1)}%${reset}`;
2326
+ }
2313
2327
  function formatTokenLimitText(usage) {
2314
- const fiveH = usage.fiveHourUtilization.toFixed(1);
2315
- const sevenD = usage.sevenDayUtilization.toFixed(1);
2316
- const emoji = utilizationEmoji(Math.max(usage.fiveHourUtilization, usage.sevenDayUtilization));
2317
- const fiveHReset = formatResetTimeJST(usage.fiveHourResetsAt);
2318
- const sevenDReset = formatResetTimeJST(usage.sevenDayResetsAt);
2319
- return ` | ${emoji} 5h: ${fiveH}% (reset: ${fiveHReset}) / 7d: ${sevenD}% (reset: ${sevenDReset})`;
2320
- }
2321
- async function buildTokenLimitText() {
2322
- const usage = await fetchUsageInfo();
2323
- if (!usage) return "";
2328
+ const parts = [
2329
+ formatWindow("5h", usage.fiveHourUtilization, usage.fiveHourResetsAt),
2330
+ formatWindow("7d", usage.sevenDayUtilization, usage.sevenDayResetsAt)
2331
+ ].filter((p) => p !== null);
2332
+ if (parts.length === 0) return "";
2333
+ const values = [usage.fiveHourUtilization, usage.sevenDayUtilization].filter((v) => v !== null);
2334
+ return ` | ${utilizationEmoji(Math.max(...values))} ${parts.join(" / ")}`;
2335
+ }
2336
+ async function buildTokenLimitText(fresh = false) {
2337
+ const usage = await fetchUsageInfo(fresh);
2338
+ if (!usage) return null;
2324
2339
  writeRuncatUsage(usage);
2325
2340
  return formatTokenLimitText(usage);
2326
2341
  }
@@ -2348,7 +2363,7 @@ function buildTaskNotificationText(params) {
2348
2363
  return `${sessionUrlPrefix}${body}`;
2349
2364
  }
2350
2365
  async function notifyTaskCompleted(workerName, repoName, id, title, url, output, cloud) {
2351
- const tokenText = await buildTokenLimitText();
2366
+ const tokenText = await buildTokenLimitText() ?? "";
2352
2367
  await send({
2353
2368
  text: buildTaskNotificationText({
2354
2369
  status: "completed",
@@ -2364,7 +2379,7 @@ async function notifyTaskCompleted(workerName, repoName, id, title, url, output,
2364
2379
  });
2365
2380
  }
2366
2381
  async function notifyTaskFailed(workerName, repoName, id, title, url, output, cloud) {
2367
- const tokenText = await buildTokenLimitText();
2382
+ const tokenText = await buildTokenLimitText() ?? "";
2368
2383
  await send({
2369
2384
  text: buildTaskNotificationText({
2370
2385
  status: "failed",
@@ -3179,6 +3194,7 @@ export {
3179
3194
  waitForAllProcesses,
3180
3195
  shutdown,
3181
3196
  generateWorktreeName,
3197
+ USAGE_POLL_INTERVAL_SECONDS,
3182
3198
  send,
3183
3199
  buildTokenLimitText,
3184
3200
  notifyTaskCompleted,
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  INHERIT_CONFIG_KEY,
8
8
  LOCAL_CONFIG_PATH,
9
9
  SCHEDULED_WORKER_NAMES,
10
+ USAGE_POLL_INTERVAL_SECONDS,
10
11
  addAssignee,
11
12
  addLabel,
12
13
  bodyMentionsIssue,
@@ -64,7 +65,7 @@ import {
64
65
  shutdown,
65
66
  waitForAllProcesses,
66
67
  writeLastRun
67
- } from "./chunk-R4T3CDMO.js";
68
+ } from "./chunk-3WIASXSK.js";
68
69
  import {
69
70
  UserConfigError,
70
71
  assertCloudCompatibleCommand,
@@ -1822,15 +1823,18 @@ if (hasProjectFilter()) {
1822
1823
  await cloudSetup({ force: process.argv.slice(3).includes("--force") });
1823
1824
  })();
1824
1825
  } else if (workerType === "usage") {
1825
- (async () => {
1826
- const text = await buildTokenLimitText();
1827
- if (!text) {
1828
- console.error("Failed to fetch usage info");
1829
- process.exit(1);
1826
+ const pollUsage = async (notify) => {
1827
+ const text = await buildTokenLimitText(true);
1828
+ if (text === null) {
1829
+ console.error(`[usage] ${(/* @__PURE__ */ new Date()).toISOString()} Failed to fetch usage info`);
1830
+ return;
1830
1831
  }
1831
- console.log(text.trim());
1832
- await send({ text: `\u{1F4CA} Usage${text}` });
1833
- })();
1832
+ console.log(`[usage] ${(/* @__PURE__ */ new Date()).toISOString()}${text}`);
1833
+ if (notify) await send({ text: `\u{1F4CA} Usage${text}` });
1834
+ };
1835
+ void pollUsage(true);
1836
+ setInterval(() => void pollUsage(false), USAGE_POLL_INTERVAL_SECONDS * 1e3);
1837
+ console.log(`[usage] polling every ${USAGE_POLL_INTERVAL_SECONDS}s (Ctrl-C to stop)`);
1834
1838
  } else if (workerType === "list-workers") {
1835
1839
  for (const e of PRESET_WORKERS) printWorkerRow(e.definition.name, "preset", "preset");
1836
1840
  for (const c of customWorkers) printWorkerRow(c.definition.name, "custom", c.source);
package/dist/lib.js CHANGED
@@ -55,7 +55,7 @@ import {
55
55
  removeWorktreeByBranch,
56
56
  run,
57
57
  syncDefaultBranch
58
- } from "./chunk-R4T3CDMO.js";
58
+ } from "./chunk-3WIASXSK.js";
59
59
  import {
60
60
  getPermissionMode,
61
61
  getRunMode,
package/dist/runcat.d.ts CHANGED
@@ -12,9 +12,9 @@ export interface RuncatSnapshot {
12
12
  lastUpdatedDate: string;
13
13
  }
14
14
  /** 'HH:MM' 形式。日を跨ぐ場合は 'MM/DD HH:MM' として日付も付ける。不正値は空文字。 */
15
- export declare function resetStamp(isoString: string, now?: Date): string;
15
+ export declare function resetStamp(isoString: string | null, now?: Date): string;
16
16
  /** 'HH' のみ。バーのように幅が限られる用途向け。不正値は空文字。 */
17
- export declare function resetHour(isoString: string): string;
17
+ export declare function resetHour(isoString: string | null): string;
18
18
  export declare function buildRuncatSnapshot(usage: UsageInfo, now?: Date): RuncatSnapshot;
19
19
  /**
20
20
  * RunCat Neo 用のスナップショットを書き出す。
package/dist/slack.d.ts CHANGED
@@ -1,16 +1,30 @@
1
+ /** `usage` コマンドが利用状況をポーリングする間隔 */
2
+ export declare const USAGE_POLL_INTERVAL_SECONDS = 360;
1
3
  export declare function send(payload: Record<string, unknown>): Promise<void>;
2
4
  export interface UsageInfo {
3
- fiveHourUtilization: number;
4
- fiveHourResetsAt: string;
5
- sevenDayUtilization: number;
6
- sevenDayResetsAt: string;
5
+ fiveHourUtilization: number | null;
6
+ fiveHourResetsAt: string | null;
7
+ sevenDayUtilization: number | null;
8
+ sevenDayResetsAt: string | null;
7
9
  }
10
+ interface UsageWindow {
11
+ utilization?: number | null;
12
+ resets_at?: string | null;
13
+ }
14
+ /** usage API のレスポンスを UsageInfo へ。欠けている窓は null にする(throw しない) */
15
+ export declare function parseUsageResponse(body: {
16
+ five_hour?: UsageWindow | null;
17
+ seven_day?: UsageWindow | null;
18
+ }): UsageInfo;
19
+ /** 返ってきた窓だけを並べる。どちらも無ければ空文字 */
20
+ export declare function formatTokenLimitText(usage: UsageInfo): string;
8
21
  /**
9
22
  * 現在の利用状況を取得し、RunCat Neo 用スナップショットを更新したうえで通知に添えるテキストを返す。
10
23
  * ワーカーの完了/失敗通知はすべてここを通るため、タスクが終わるたびに RunCat 側も最新化される
11
24
  * (Slack webhook 未設定でも通知が no-op になるだけで、スナップショットの更新は行われる)。
25
+ * 取得に失敗したときだけ null を返す(どの枠も返らない正常応答は空文字)。
12
26
  */
13
- export declare function buildTokenLimitText(): Promise<string>;
27
+ export declare function buildTokenLimitText(fresh?: boolean): Promise<string | null>;
14
28
  /**
15
29
  * 通知本文へ載せる output を末尾1000文字に切り詰める。
16
30
  * 案内文(CLOUD_FAILURE_GUIDANCE)は appendCloudFailureGuidance が output の先頭に
@@ -44,3 +58,4 @@ export declare function notifyTaskFailed(workerName: string, repoName: string, i
44
58
  sessionId?: string;
45
59
  }): Promise<void>;
46
60
  export declare function notifyError(workerName: string, repoName: string, error: unknown): Promise<void>;
61
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-task-worker",
3
- "version": "0.137.0",
3
+ "version": "0.138.0",
4
4
  "description": "CLI tool that polls GitHub Issues/PRs and delegates work to Claude CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",