claude-task-worker 0.136.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>`
@@ -361,8 +361,11 @@ async function fetchPRChecks(prNumber) {
361
361
  if (!output) return [];
362
362
  return JSON.parse(output);
363
363
  }
364
+ function excludePrsByLabels(prs, excludeLabels) {
365
+ return prs.filter((pr) => !pr.labels.some((l) => excludeLabels.includes(l.name)));
366
+ }
367
+ var PR_LIST_FETCH_LIMIT = 50;
364
368
  async function listPullRequestsWithChecks(assignee, label, excludeLabels = [], limit = 5) {
365
- const search = ["sort:created-asc", ...excludeLabels.map((l) => `-label:"${l}"`)].join(" ");
366
369
  const args = [
367
370
  "pr",
368
371
  "list",
@@ -371,9 +374,9 @@ async function listPullRequestsWithChecks(assignee, label, excludeLabels = [], l
371
374
  "--json",
372
375
  "number,title,labels,headRefName",
373
376
  "--search",
374
- search,
377
+ "sort:created-asc",
375
378
  "--limit",
376
- String(limit)
379
+ String(PR_LIST_FETCH_LIMIT)
377
380
  ];
378
381
  if (assignee) {
379
382
  args.push("--assignee", assignee);
@@ -382,7 +385,7 @@ async function listPullRequestsWithChecks(assignee, label, excludeLabels = [], l
382
385
  args.push("--label", label);
383
386
  }
384
387
  const output = await execGh(args);
385
- const prs = JSON.parse(output);
388
+ const prs = excludePrsByLabels(JSON.parse(output), excludeLabels).slice(0, limit);
386
389
  const withChecks = await Promise.all(prs.map(async (pr) => ({ ...pr, checks: await fetchPRChecks(pr.number) })));
387
390
  return withChecks.filter((pr) => isCICompleted(pr.checks));
388
391
  }
@@ -2164,6 +2167,7 @@ function ceilToMinute(date) {
2164
2167
  return remainder === 0 ? date : new Date(date.getTime() + (MINUTE_MS - remainder));
2165
2168
  }
2166
2169
  function parseResetsAt(isoString) {
2170
+ if (!isoString) return null;
2167
2171
  const date = new Date(isoString);
2168
2172
  return Number.isNaN(date.getTime()) ? null : ceilToMinute(date);
2169
2173
  }
@@ -2189,15 +2193,17 @@ function metric(title, pct, resetsAt, now) {
2189
2193
  };
2190
2194
  }
2191
2195
  function buildRuncatSnapshot(usage, now = /* @__PURE__ */ new Date()) {
2192
- const bar = `${formatG(usage.fiveHourUtilization)}/${formatG(usage.sevenDayUtilization)}`;
2193
- 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]) : "";
2194
2203
  return {
2195
2204
  title: "Claude Code",
2196
2205
  symbol: "staroflife",
2197
- metrics: [
2198
- metric("5h", usage.fiveHourUtilization, usage.fiveHourResetsAt, now),
2199
- metric("7d", usage.sevenDayUtilization, usage.sevenDayResetsAt, now)
2200
- ],
2206
+ metrics: present.map(([title, pct, resetsAt]) => metric(title, pct, resetsAt, now)),
2201
2207
  metricsBarValue: stamp ? `${bar} \u21BB${stamp}` : bar,
2202
2208
  lastUpdatedDate: now.toISOString().replace(/\.\d{3}Z$/, "Z")
2203
2209
  };
@@ -2211,7 +2217,10 @@ function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
2211
2217
  writeFileSync3(tmp, JSON.stringify(snapshot), "utf-8");
2212
2218
  renameSync2(tmp, outFile);
2213
2219
  } catch (err) {
2214
- rmSync(tmp, { force: true });
2220
+ try {
2221
+ rmSync(tmp, { force: true });
2222
+ } catch {
2223
+ }
2215
2224
  console.error(`[runcat] Failed to write ${outFile}: ${err}`);
2216
2225
  }
2217
2226
  }
@@ -2220,6 +2229,7 @@ function writeRuncatUsage(usage, outFile = RUNCAT_OUT_FILE) {
2220
2229
  var execAsync = promisify2(exec);
2221
2230
  var WEBHOOK_URL = process.env.CLAUDE_TASK_WORKER_SLACK_WEBHOOK_URL;
2222
2231
  var USAGE_CACHE_PATH = "/tmp/claude-usage-cache.json";
2232
+ var USAGE_POLL_INTERVAL_SECONDS = 360;
2223
2233
  var USAGE_CACHE_TTL_SECONDS = 360;
2224
2234
  async function send(payload) {
2225
2235
  if (!WEBHOOK_URL) return;
@@ -2233,6 +2243,14 @@ async function send(payload) {
2233
2243
  console.error(`[slack] Failed to send notification: ${err}`);
2234
2244
  }
2235
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
+ }
2236
2254
  function extractToken(credentials) {
2237
2255
  if (typeof credentials === "string") return credentials;
2238
2256
  return credentials.claudeAiOauth?.accessToken ?? credentials.oauth_token ?? credentials.access_token ?? "";
@@ -2263,8 +2281,8 @@ function writeUsageCache(data) {
2263
2281
  } catch {
2264
2282
  }
2265
2283
  }
2266
- async function fetchUsageInfo() {
2267
- const cached = readUsageCache();
2284
+ async function fetchUsageInfo(fresh) {
2285
+ const cached = fresh ? null : readUsageCache();
2268
2286
  if (cached) return cached;
2269
2287
  try {
2270
2288
  const token = await getOAuthToken();
@@ -2278,13 +2296,7 @@ async function fetchUsageInfo() {
2278
2296
  console.error(`[slack] Usage API returned ${res.status}`);
2279
2297
  return null;
2280
2298
  }
2281
- const body = await res.json();
2282
- const data = {
2283
- fiveHourUtilization: body.five_hour.utilization,
2284
- fiveHourResetsAt: body.five_hour.resets_at,
2285
- sevenDayUtilization: body.seven_day.utilization,
2286
- sevenDayResetsAt: body.seven_day.resets_at
2287
- };
2299
+ const data = parseUsageResponse(await res.json());
2288
2300
  writeUsageCache(data);
2289
2301
  return data;
2290
2302
  } catch (err) {
@@ -2307,17 +2319,23 @@ function formatResetTimeJST(isoString) {
2307
2319
  minute: "2-digit"
2308
2320
  });
2309
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
+ }
2310
2327
  function formatTokenLimitText(usage) {
2311
- const fiveH = usage.fiveHourUtilization.toFixed(1);
2312
- const sevenD = usage.sevenDayUtilization.toFixed(1);
2313
- const emoji = utilizationEmoji(Math.max(usage.fiveHourUtilization, usage.sevenDayUtilization));
2314
- const fiveHReset = formatResetTimeJST(usage.fiveHourResetsAt);
2315
- const sevenDReset = formatResetTimeJST(usage.sevenDayResetsAt);
2316
- return ` | ${emoji} 5h: ${fiveH}% (reset: ${fiveHReset}) / 7d: ${sevenD}% (reset: ${sevenDReset})`;
2317
- }
2318
- async function buildTokenLimitText() {
2319
- const usage = await fetchUsageInfo();
2320
- 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;
2321
2339
  writeRuncatUsage(usage);
2322
2340
  return formatTokenLimitText(usage);
2323
2341
  }
@@ -2345,7 +2363,7 @@ function buildTaskNotificationText(params) {
2345
2363
  return `${sessionUrlPrefix}${body}`;
2346
2364
  }
2347
2365
  async function notifyTaskCompleted(workerName, repoName, id, title, url, output, cloud) {
2348
- const tokenText = await buildTokenLimitText();
2366
+ const tokenText = await buildTokenLimitText() ?? "";
2349
2367
  await send({
2350
2368
  text: buildTaskNotificationText({
2351
2369
  status: "completed",
@@ -2361,7 +2379,7 @@ async function notifyTaskCompleted(workerName, repoName, id, title, url, output,
2361
2379
  });
2362
2380
  }
2363
2381
  async function notifyTaskFailed(workerName, repoName, id, title, url, output, cloud) {
2364
- const tokenText = await buildTokenLimitText();
2382
+ const tokenText = await buildTokenLimitText() ?? "";
2365
2383
  await send({
2366
2384
  text: buildTaskNotificationText({
2367
2385
  status: "failed",
@@ -3176,6 +3194,7 @@ export {
3176
3194
  waitForAllProcesses,
3177
3195
  shutdown,
3178
3196
  generateWorktreeName,
3197
+ USAGE_POLL_INTERVAL_SECONDS,
3179
3198
  send,
3180
3199
  buildTokenLimitText,
3181
3200
  notifyTaskCompleted,
package/dist/gh.d.ts CHANGED
@@ -85,6 +85,11 @@ export interface PullRequestWithChecks extends PullRequest {
85
85
  checks: PRCheck[];
86
86
  }
87
87
  export declare function isCICompleted(checks: PRCheck[]): boolean;
88
+ export declare function excludePrsByLabels<T extends {
89
+ labels: {
90
+ name: string;
91
+ }[];
92
+ }>(prs: T[], excludeLabels: string[]): T[];
88
93
  export declare function listPullRequestsWithChecks(assignee?: string, label?: string, excludeLabels?: string[], limit?: number): Promise<PullRequestWithChecks[]>;
89
94
  export declare function addLabel(type: "issue" | "pr", number: number, label: string): Promise<void>;
90
95
  export declare function addAssignee(type: "issue" | "pr", number: number, login: string): Promise<void>;
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-NWYH24MB.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-NWYH24MB.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.136.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",