omniagent 0.1.7 → 0.1.9

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/dist/cli.js CHANGED
@@ -234,8 +234,9 @@ function buildValidationError(resolution, status, message) {
234
234
  errorMessage: message
235
235
  };
236
236
  }
237
- async function validateAgentsDir(repoRoot, agentsDir) {
237
+ async function validateAgentsDir(repoRoot, agentsDir, options = {}) {
238
238
  const resolution = resolveAgentsDir(repoRoot, agentsDir);
239
+ const requireWrite = options.requireWrite ?? true;
239
240
  const buildNotDirectoryError = () => buildValidationError(
240
241
  resolution,
241
242
  "notDirectory",
@@ -269,14 +270,16 @@ async function validateAgentsDir(repoRoot, agentsDir) {
269
270
  return buildNotDirectoryError();
270
271
  }
271
272
  try {
272
- await access(resolution.resolvedPath, constants.R_OK | constants.W_OK | constants.X_OK);
273
+ const requiredAccess = constants.R_OK | constants.X_OK | (requireWrite ? constants.W_OK : 0);
274
+ await access(resolution.resolvedPath, requiredAccess);
273
275
  } catch (error) {
274
276
  const code = error.code;
275
277
  if (code === "EACCES" || code === "EPERM") {
278
+ const requirement = requireWrite ? "readable, writable, or searchable" : "readable or searchable";
276
279
  return buildValidationError(
277
280
  resolution,
278
281
  "permissionDenied",
279
- `Agents directory is not readable, writable, or searchable: ${resolution.resolvedPath}. Check permissions or choose another directory.`
282
+ `Agents directory is not ${requirement}: ${resolution.resolvedPath}. Check permissions or choose another directory.`
280
283
  );
281
284
  }
282
285
  throw error;
@@ -1308,6 +1311,20 @@ const claudeTarget = {
1308
1311
  instructions: {
1309
1312
  filename: "CLAUDE.md"
1310
1313
  }
1314
+ },
1315
+ usage: {
1316
+ windows: ["hourly", "weekly"],
1317
+ launch: {
1318
+ command: "claude",
1319
+ // Haiku keeps the interactive usage probe on Claude's lowest-cost model family.
1320
+ args: ["--model", "haiku"],
1321
+ cheapModel: "haiku",
1322
+ timeoutMs: 6e4
1323
+ },
1324
+ extract: async (context) => {
1325
+ const { extractClaudeUsage } = await import("./claude-Dmv_YFKX.js");
1326
+ return extractClaudeUsage(context);
1327
+ }
1311
1328
  }
1312
1329
  };
1313
1330
  const codexTarget = {
@@ -1367,6 +1384,27 @@ const codexTarget = {
1367
1384
  filename: "AGENTS.md",
1368
1385
  group: "agents"
1369
1386
  }
1387
+ },
1388
+ usage: {
1389
+ windows: ["hourly", "weekly"],
1390
+ launch: {
1391
+ command: "codex",
1392
+ // Usage probing does not need plugins/apps; disabling them avoids MCP startup work.
1393
+ args: [
1394
+ "--no-alt-screen",
1395
+ "--disable",
1396
+ "apps",
1397
+ "--disable",
1398
+ "computer_use",
1399
+ "--disable",
1400
+ "plugins"
1401
+ ],
1402
+ timeoutMs: 6e4
1403
+ },
1404
+ extract: async (context) => {
1405
+ const { extractCodexUsage } = await import("./codex-D1RuzsY6.js");
1406
+ return extractCodexUsage(context);
1407
+ }
1370
1408
  }
1371
1409
  };
1372
1410
  const FRONTMATTER_MARKER$1 = "---";
@@ -1558,6 +1596,19 @@ const geminiTarget = {
1558
1596
  instructions: {
1559
1597
  filename: "GEMINI.md"
1560
1598
  }
1599
+ },
1600
+ usage: {
1601
+ windows: ["model"],
1602
+ launch: {
1603
+ command: "gemini",
1604
+ // /model inspection does not need a model override; Gemini requires session-scoped trust.
1605
+ args: ["--skip-trust"],
1606
+ timeoutMs: 7e4
1607
+ },
1608
+ extract: async (context) => {
1609
+ const { extractGeminiUsage } = await import("./gemini-CskI3Qjp.js");
1610
+ return extractGeminiUsage(context);
1611
+ }
1561
1612
  }
1562
1613
  };
1563
1614
  const BUILTIN_TARGETS = [
@@ -2513,6 +2564,39 @@ function validateCliDefinition(cli, label, errors) {
2513
2564
  errors.push(`${label}.translate must be a function when provided.`);
2514
2565
  }
2515
2566
  }
2567
+ function validateUsageDefinition(usage, label, errors) {
2568
+ if (usage === void 0) {
2569
+ return;
2570
+ }
2571
+ if (!isPlainObject(usage)) {
2572
+ errors.push(`${label} must be an object.`);
2573
+ return;
2574
+ }
2575
+ validateStringArray(usage.windows, `${label}.windows`, errors);
2576
+ if (usage.launch !== void 0) {
2577
+ if (!isPlainObject(usage.launch)) {
2578
+ errors.push(`${label}.launch must be an object.`);
2579
+ } else {
2580
+ if (usage.launch.command !== void 0 && normalizeString(usage.launch.command) === null) {
2581
+ errors.push(`${label}.launch.command must be a non-empty string when provided.`);
2582
+ }
2583
+ if (usage.launch.args !== void 0) {
2584
+ validateStringArray(usage.launch.args, `${label}.launch.args`, errors, {
2585
+ allowEmpty: true
2586
+ });
2587
+ }
2588
+ if (usage.launch.timeoutMs !== void 0 && (typeof usage.launch.timeoutMs !== "number" || !Number.isFinite(usage.launch.timeoutMs) || usage.launch.timeoutMs <= 0)) {
2589
+ errors.push(`${label}.launch.timeoutMs must be a positive number when provided.`);
2590
+ }
2591
+ if (usage.launch.cheapModel !== void 0 && normalizeString(usage.launch.cheapModel) === null) {
2592
+ errors.push(`${label}.launch.cheapModel must be a non-empty string when provided.`);
2593
+ }
2594
+ }
2595
+ }
2596
+ if (typeof usage.extract !== "function") {
2597
+ errors.push(`${label}.extract must be a function.`);
2598
+ }
2599
+ }
2516
2600
  function validateTemplate(value, label, allowed, errors) {
2517
2601
  if (typeof value === "function") {
2518
2602
  return;
@@ -2745,6 +2829,7 @@ function validateTargetConfig(options) {
2745
2829
  }
2746
2830
  validateOutputs(entry2.outputs, `${label}.outputs`, errors);
2747
2831
  validateCliDefinition(entry2.cli, `${label}.cli`, errors);
2832
+ validateUsageDefinition(entry2.usage, `${label}.usage`, errors);
2748
2833
  }
2749
2834
  }
2750
2835
  }
@@ -2771,6 +2856,19 @@ function cloneOutputs(outputs) {
2771
2856
  function cloneCli(cli) {
2772
2857
  return cli ? { ...cli } : void 0;
2773
2858
  }
2859
+ function cloneUsage(usage) {
2860
+ if (!usage) {
2861
+ return void 0;
2862
+ }
2863
+ return {
2864
+ ...usage,
2865
+ windows: [...usage.windows],
2866
+ launch: usage.launch ? {
2867
+ ...usage.launch,
2868
+ args: usage.launch.args ? [...usage.launch.args] : void 0
2869
+ } : void 0
2870
+ };
2871
+ }
2774
2872
  function mergeOutputs(base, override) {
2775
2873
  const merged = { ...cloneOutputs(base) };
2776
2874
  if (!override) {
@@ -2826,6 +2924,7 @@ function resolveTargets(options) {
2826
2924
  aliases: builtIn.aliases ?? [],
2827
2925
  outputs: cloneOutputs(builtIn.outputs),
2828
2926
  cli: cloneCli(builtIn.cli),
2927
+ usage: cloneUsage(builtIn.usage),
2829
2928
  hooks: builtIn.hooks,
2830
2929
  isBuiltIn: true,
2831
2930
  isCustomized: false
@@ -2841,7 +2940,8 @@ function resolveTargets(options) {
2841
2940
  displayName: customTarget.displayName ?? inherited?.displayName ?? customTarget.id,
2842
2941
  aliases: customTarget.aliases ?? inherited?.aliases ?? [],
2843
2942
  outputs: mergedOutputs,
2844
- cli: customTarget.cli ?? inherited?.cli,
2943
+ cli: cloneCli(customTarget.cli ?? inherited?.cli),
2944
+ usage: cloneUsage(customTarget.usage ?? inherited?.usage),
2845
2945
  hooks: customTarget.hooks ?? inherited?.hooks,
2846
2946
  isBuiltIn: true,
2847
2947
  isCustomized: true
@@ -2854,6 +2954,7 @@ function resolveTargets(options) {
2854
2954
  aliases: customTarget.aliases ?? [],
2855
2955
  outputs: cloneOutputs(customTarget.outputs),
2856
2956
  cli: cloneCli(customTarget.cli),
2957
+ usage: cloneUsage(customTarget.usage),
2857
2958
  hooks: customTarget.hooks,
2858
2959
  isBuiltIn: true,
2859
2960
  isCustomized: true
@@ -2873,7 +2974,8 @@ function resolveTargets(options) {
2873
2974
  displayName: target.displayName ?? inherited?.displayName ?? target.id,
2874
2975
  aliases: target.aliases ?? inherited?.aliases ?? [],
2875
2976
  outputs: mergedOutputs,
2876
- cli: target.cli ?? inherited?.cli,
2977
+ cli: cloneCli(target.cli ?? inherited?.cli),
2978
+ usage: cloneUsage(target.usage ?? inherited?.usage),
2877
2979
  hooks: target.hooks ?? inherited?.hooks,
2878
2980
  isBuiltIn: false,
2879
2981
  isCustomized: true
@@ -8274,7 +8376,7 @@ function getTargetCliCommands(target) {
8274
8376
  add(target.cli?.modes?.oneShot?.command);
8275
8377
  return commands;
8276
8378
  }
8277
- async function checkCliOnPath(command) {
8379
+ async function checkCliOnPath(command, options = {}) {
8278
8380
  const normalized = normalizeCommand(command);
8279
8381
  if (!normalized) {
8280
8382
  return { command: command ?? "", result: "unavailable" };
@@ -8285,7 +8387,7 @@ async function checkCliOnPath(command) {
8285
8387
  if (hasPathSeparator(normalized)) {
8286
8388
  for (const candidate of candidates) {
8287
8389
  const check = await checkExecutable(candidate);
8288
- if (check.status === "available") {
8390
+ if (check.status === "available" && await isValidCandidate(check.resolvedPath, options.validateCandidate)) {
8289
8391
  return { command: normalized, result: "available", resolvedPath: check.resolvedPath };
8290
8392
  }
8291
8393
  if (check.status === "inconclusive") {
@@ -8313,7 +8415,7 @@ async function checkCliOnPath(command) {
8313
8415
  for (const candidate of candidates) {
8314
8416
  const fullPath = path.join(entry2, candidate);
8315
8417
  const check = await checkExecutable(fullPath);
8316
- if (check.status === "available") {
8418
+ if (check.status === "available" && await isValidCandidate(check.resolvedPath, options.validateCandidate)) {
8317
8419
  return { command: normalized, result: "available", resolvedPath: check.resolvedPath };
8318
8420
  }
8319
8421
  if (check.status === "inconclusive") {
@@ -8330,6 +8432,16 @@ async function checkCliOnPath(command) {
8330
8432
  }
8331
8433
  return { command: normalized, result: "unavailable" };
8332
8434
  }
8435
+ async function isValidCandidate(resolvedPath, validateCandidate) {
8436
+ if (!validateCandidate) {
8437
+ return true;
8438
+ }
8439
+ try {
8440
+ return await validateCandidate(resolvedPath);
8441
+ } catch {
8442
+ return false;
8443
+ }
8444
+ }
8333
8445
  async function checkTargetAvailability(target) {
8334
8446
  const commands = getTargetCliCommands(target);
8335
8447
  if (commands.length === 0) {
@@ -8366,7 +8478,7 @@ const DEFAULT_SUPPORTED_TARGETS = BUILTIN_TARGETS.map((target) => target.id).joi
8366
8478
  const LOCAL_CATEGORIES = ["skills", "commands", "agents", "instructions"];
8367
8479
  const LOCAL_CATEGORY_SET = new Set(LOCAL_CATEGORIES);
8368
8480
  const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
8369
- function parseList(value) {
8481
+ function parseList$1(value) {
8370
8482
  if (!value) {
8371
8483
  return [];
8372
8484
  }
@@ -8435,7 +8547,7 @@ function parseExcludeLocal(value) {
8435
8547
  if (value === true) {
8436
8548
  return { excludeAll: true, categories: new Set(LOCAL_CATEGORIES), invalid: [] };
8437
8549
  }
8438
- const list = parseList(value);
8550
+ const list = parseList$1(value);
8439
8551
  if (list.length === 0) {
8440
8552
  return { excludeAll: true, categories: new Set(LOCAL_CATEGORIES), invalid: [] };
8441
8553
  }
@@ -9453,8 +9565,8 @@ Config: auto-discovered as omniagent.config.(ts|mts|cts|js|mjs|cjs) in the agent
9453
9565
  ),
9454
9566
  handler: async (argv) => {
9455
9567
  try {
9456
- const skipList = parseList(argv.skip);
9457
- const onlyList = parseList(argv.only);
9568
+ const skipList = parseList$1(argv.skip);
9569
+ const onlyList = parseList$1(argv.only);
9458
9570
  const excludeLocalSelection = parseExcludeLocal(argv.excludeLocal);
9459
9571
  if (excludeLocalSelection.invalid.length > 0) {
9460
9572
  const invalidList = excludeLocalSelection.invalid.join(", ");
@@ -10209,6 +10321,1272 @@ Config: auto-discovered as omniagent.config.(ts|mts|cts|js|mjs|cjs) in the agent
10209
10321
  }
10210
10322
  }
10211
10323
  };
10324
+ const MONTHS = /* @__PURE__ */ new Map([
10325
+ ["jan", 0],
10326
+ ["january", 0],
10327
+ ["feb", 1],
10328
+ ["february", 1],
10329
+ ["mar", 2],
10330
+ ["march", 2],
10331
+ ["apr", 3],
10332
+ ["april", 3],
10333
+ ["may", 4],
10334
+ ["jun", 5],
10335
+ ["june", 5],
10336
+ ["jul", 6],
10337
+ ["july", 6],
10338
+ ["aug", 7],
10339
+ ["august", 7],
10340
+ ["sep", 8],
10341
+ ["sept", 8],
10342
+ ["september", 8],
10343
+ ["oct", 9],
10344
+ ["october", 9],
10345
+ ["nov", 10],
10346
+ ["november", 10],
10347
+ ["dec", 11],
10348
+ ["december", 11]
10349
+ ]);
10350
+ const ESCAPE_CHARACTER = String.fromCharCode(27);
10351
+ const BELL_CHARACTER = String.fromCharCode(7);
10352
+ const OSC_SEQUENCE_PATTERN = new RegExp(
10353
+ `${escapeRegExp(ESCAPE_CHARACTER)}\\][\\s\\S]*?(?:${escapeRegExp(BELL_CHARACTER)}|${escapeRegExp(ESCAPE_CHARACTER)}\\\\)`,
10354
+ "g"
10355
+ );
10356
+ const CSI_SEQUENCE_PATTERN = new RegExp(
10357
+ `${escapeRegExp(ESCAPE_CHARACTER)}\\[[0-?]*[ -/]*[@-~]`,
10358
+ "g"
10359
+ );
10360
+ const CHARSET_SEQUENCE_PATTERN = new RegExp(
10361
+ `${escapeRegExp(ESCAPE_CHARACTER)}[()][A-Za-z0-9]`,
10362
+ "g"
10363
+ );
10364
+ const MODE_SEQUENCE_PATTERN = new RegExp(`${escapeRegExp(ESCAPE_CHARACTER)}[=>]`, "g");
10365
+ function makeUsageLimit(options) {
10366
+ const agent = options.agent ?? options.targetId;
10367
+ const resetText = emptyToNull(options.resetText);
10368
+ const window = normalizeUsageWindow(options.window);
10369
+ return {
10370
+ id: [options.targetId, options.scope, window, options.modelId].filter(Boolean).join("."),
10371
+ targetId: options.targetId,
10372
+ agent,
10373
+ scope: options.scope,
10374
+ window,
10375
+ label: options.label,
10376
+ modelId: options.modelId,
10377
+ modelLabel: options.modelLabel,
10378
+ percentUsed: options.percentUsed,
10379
+ percentRemaining: options.percentRemaining,
10380
+ resetAt: parseResetAt(resetText, {
10381
+ now: options.now,
10382
+ sourceTimeZone: options.resetSourceTimeZone ?? "local"
10383
+ }),
10384
+ resetText,
10385
+ raw: options.raw ?? ""
10386
+ };
10387
+ }
10388
+ function parsePercentUsed(value) {
10389
+ return parsePercent(value, /(\d+(?:\.\d+)?)\s*%\s*used/i);
10390
+ }
10391
+ function parsePercentRemaining(value) {
10392
+ return parsePercent(value, /(\d+(?:\.\d+)?)\s*%\s*(?:left|remaining)/i);
10393
+ }
10394
+ function parsePercent(value, pattern = /(\d+(?:\.\d+)?)\s*%/) {
10395
+ const match = pattern.exec(value ?? "");
10396
+ if (match == null) {
10397
+ return null;
10398
+ }
10399
+ return Number(match[1]);
10400
+ }
10401
+ function parseResetText(value) {
10402
+ const match = /\((resets[^)]*)\)/i.exec(value ?? "");
10403
+ return match == null ? null : match[1].trim();
10404
+ }
10405
+ function normalizeUsageWindow(window) {
10406
+ const normalized = window.trim().toLowerCase();
10407
+ if (normalized === "5h" || normalized === "five_hour" || normalized === "five-hour" || normalized === "session" || normalized === "hourly") {
10408
+ return "hourly";
10409
+ }
10410
+ if (normalized === "week" || normalized === "current_week" || normalized === "weekly") {
10411
+ return "weekly";
10412
+ }
10413
+ if (normalized === "model") {
10414
+ return "model";
10415
+ }
10416
+ return normalized;
10417
+ }
10418
+ function cleanControlOutput(raw) {
10419
+ return raw.replace(OSC_SEQUENCE_PATTERN, "").replace(CSI_SEQUENCE_PATTERN, "").replace(CHARSET_SEQUENCE_PATTERN, "").replace(MODE_SEQUENCE_PATTERN, "").replace(/\r/g, "\n").split("\n").map((line) => line.replace(/[ \t]+$/g, "")).join("\n");
10420
+ }
10421
+ function compactLines(text) {
10422
+ return text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
10423
+ }
10424
+ function parseResetAt(resetText, options = {}) {
10425
+ const now = options.now ?? /* @__PURE__ */ new Date();
10426
+ const sourceTimeZone = options.sourceTimeZone ?? "local";
10427
+ const text = normalizeResetText(resetText);
10428
+ if (!text) {
10429
+ return null;
10430
+ }
10431
+ return parseHourMinuteOnDayMonth(text, now, sourceTimeZone) ?? parseMonthDayAtTime(text, now, sourceTimeZone) ?? parseDayMonthAtTime(text, now, sourceTimeZone) ?? parseTimeOnly(text, now, sourceTimeZone);
10432
+ }
10433
+ function parseHourMinuteOnDayMonth(text, now, sourceTimeZone) {
10434
+ const match = /^(\d{1,2}):(\d{2})\s+on\s+(\d{1,2})\s+([A-Za-z]+)$/i.exec(text);
10435
+ if (match == null) {
10436
+ return null;
10437
+ }
10438
+ const [, hour, minute, day, monthName] = match;
10439
+ const month = parseMonth(monthName);
10440
+ if (month == null) {
10441
+ return null;
10442
+ }
10443
+ return buildFutureDate(now, sourceTimeZone, {
10444
+ month,
10445
+ day: Number(day),
10446
+ hour: Number(hour),
10447
+ minute: Number(minute)
10448
+ });
10449
+ }
10450
+ function parseMonthDayAtTime(text, now, sourceTimeZone) {
10451
+ const match = /^([A-Za-z]+)\s+(\d{1,2})(?:\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?)?$/i.exec(
10452
+ text
10453
+ );
10454
+ if (match == null) {
10455
+ return null;
10456
+ }
10457
+ const [, monthName, day, hour = "0", minute = "0", meridiem] = match;
10458
+ const month = parseMonth(monthName);
10459
+ if (month == null) {
10460
+ return null;
10461
+ }
10462
+ return buildFutureDate(now, sourceTimeZone, {
10463
+ month,
10464
+ day: Number(day),
10465
+ hour: parseHour(hour, meridiem),
10466
+ minute: Number(minute)
10467
+ });
10468
+ }
10469
+ function parseDayMonthAtTime(text, now, sourceTimeZone) {
10470
+ const match = /^(\d{1,2})\s+([A-Za-z]+)(?:\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?)?$/i.exec(
10471
+ text
10472
+ );
10473
+ if (match == null) {
10474
+ return null;
10475
+ }
10476
+ const [, day, monthName, hour = "0", minute = "0", meridiem] = match;
10477
+ const month = parseMonth(monthName);
10478
+ if (month == null) {
10479
+ return null;
10480
+ }
10481
+ return buildFutureDate(now, sourceTimeZone, {
10482
+ month,
10483
+ day: Number(day),
10484
+ hour: parseHour(hour, meridiem),
10485
+ minute: Number(minute)
10486
+ });
10487
+ }
10488
+ function parseTimeOnly(text, now, sourceTimeZone) {
10489
+ const match = /^(\d{1,2})(?::(\d{2}))?\s*(am|pm)?$/i.exec(text);
10490
+ if (match == null) {
10491
+ return null;
10492
+ }
10493
+ const [, hour, minute = "0", meridiem] = match;
10494
+ const date = sourceTimeZone === "utc" ? new Date(
10495
+ Date.UTC(
10496
+ now.getUTCFullYear(),
10497
+ now.getUTCMonth(),
10498
+ now.getUTCDate(),
10499
+ parseHour(hour, meridiem),
10500
+ Number(minute)
10501
+ )
10502
+ ) : new Date(
10503
+ now.getFullYear(),
10504
+ now.getMonth(),
10505
+ now.getDate(),
10506
+ parseHour(hour, meridiem),
10507
+ Number(minute)
10508
+ );
10509
+ if (date <= now) {
10510
+ if (sourceTimeZone === "utc") {
10511
+ date.setUTCDate(date.getUTCDate() + 1);
10512
+ } else {
10513
+ date.setDate(date.getDate() + 1);
10514
+ }
10515
+ }
10516
+ return date.toISOString();
10517
+ }
10518
+ function buildFutureDate(now, sourceTimeZone, values) {
10519
+ const date = sourceTimeZone === "utc" ? new Date(
10520
+ Date.UTC(now.getUTCFullYear(), values.month, values.day, values.hour, values.minute)
10521
+ ) : new Date(now.getFullYear(), values.month, values.day, values.hour, values.minute);
10522
+ if (date <= now) {
10523
+ if (sourceTimeZone === "utc") {
10524
+ date.setUTCFullYear(date.getUTCFullYear() + 1);
10525
+ } else {
10526
+ date.setFullYear(date.getFullYear() + 1);
10527
+ }
10528
+ }
10529
+ return date.toISOString();
10530
+ }
10531
+ function normalizeResetText(resetText) {
10532
+ return (resetText ?? "").replace(/^resets\s+/i, "").replace(/\([^)]*\)/g, "").replace(/\s+/g, " ").trim();
10533
+ }
10534
+ function parseMonth(monthName) {
10535
+ return MONTHS.get(monthName.toLowerCase()) ?? null;
10536
+ }
10537
+ function parseHour(hourValue, meridiem) {
10538
+ const hour = Number(hourValue);
10539
+ if (meridiem == null) {
10540
+ return hour;
10541
+ }
10542
+ const lower = meridiem.toLowerCase();
10543
+ if (lower === "am") {
10544
+ return hour === 12 ? 0 : hour;
10545
+ }
10546
+ if (lower === "pm") {
10547
+ return hour === 12 ? 12 : hour + 12;
10548
+ }
10549
+ return hour;
10550
+ }
10551
+ function emptyToNull(value) {
10552
+ return value == null || value === "" ? null : value;
10553
+ }
10554
+ function escapeRegExp(value) {
10555
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10556
+ }
10557
+ const USAGE_BAR_WIDTH = 12;
10558
+ const ANSI = {
10559
+ reset: "\x1B[0m",
10560
+ bold: "\x1B[1m",
10561
+ dim: "\x1B[2m",
10562
+ green: "\x1B[32m",
10563
+ yellow: "\x1B[33m",
10564
+ orange: "\x1B[38;5;208m",
10565
+ red: "\x1B[31m",
10566
+ gray: "\x1B[90m"
10567
+ };
10568
+ const DEFAULT_USAGE_TIMEOUT_MS = 3e4;
10569
+ const MS_PER_MINUTE = 6e4;
10570
+ const MS_PER_HOUR = 36e5;
10571
+ const MS_PER_DAY = 864e5;
10572
+ const DETAILED_RESET_THRESHOLD_MINUTES = 180;
10573
+ const RELATIVE_RESET_WIDTH = 6;
10574
+ const RESET_WEEKDAY_TIME_FORMATTER = new Intl.DateTimeFormat("en-US", {
10575
+ weekday: "short",
10576
+ hour: "numeric",
10577
+ minute: "2-digit"
10578
+ });
10579
+ const RESET_DATE_TIME_FORMATTER = new Intl.DateTimeFormat("en-US", {
10580
+ month: "short",
10581
+ day: "numeric",
10582
+ hour: "numeric",
10583
+ minute: "2-digit"
10584
+ });
10585
+ class UsageExtractionTimeoutError extends Error {
10586
+ constructor(timeoutMs) {
10587
+ super(`Usage extraction timed out after ${formatDuration(timeoutMs)}.`);
10588
+ this.timeoutMs = timeoutMs;
10589
+ }
10590
+ }
10591
+ function normalizeOptionalWindow(value) {
10592
+ if (value == null) {
10593
+ return null;
10594
+ }
10595
+ const trimmed = value.trim();
10596
+ return trimmed.length > 0 ? normalizeUsageWindow(trimmed) : "";
10597
+ }
10598
+ function normalizeOptionalSort(value) {
10599
+ if (value == null) {
10600
+ return null;
10601
+ }
10602
+ const normalized = value.trim().toLowerCase();
10603
+ if (!normalized) {
10604
+ return "";
10605
+ }
10606
+ return normalized === "reset" || normalized === "left" ? normalized : "";
10607
+ }
10608
+ function parseTimeoutMs(value) {
10609
+ if (value == null) {
10610
+ return void 0;
10611
+ }
10612
+ const trimmed = value.trim();
10613
+ if (!trimmed) {
10614
+ return null;
10615
+ }
10616
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m)?$/i.exec(trimmed);
10617
+ if (!match) {
10618
+ return null;
10619
+ }
10620
+ const amount = Number(match[1]);
10621
+ const unit = match[2]?.toLowerCase() ?? "s";
10622
+ const multiplier = unit === "ms" ? 1 : unit === "m" ? 6e4 : 1e3;
10623
+ const timeoutMs = amount * multiplier;
10624
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
10625
+ return null;
10626
+ }
10627
+ return Math.ceil(timeoutMs);
10628
+ }
10629
+ function resolveTargetTimeoutMs(target, cliTimeoutMs) {
10630
+ return cliTimeoutMs ?? target.usage?.launch?.timeoutMs ?? DEFAULT_USAGE_TIMEOUT_MS;
10631
+ }
10632
+ function parseList(value) {
10633
+ if (!value) {
10634
+ return [];
10635
+ }
10636
+ const rawValues = Array.isArray(value) ? value : [value];
10637
+ return rawValues.flatMap((entry2) => entry2.split(",")).map((entry2) => entry2.trim().toLowerCase()).filter(Boolean);
10638
+ }
10639
+ function formatDuration(timeoutMs) {
10640
+ if (timeoutMs % 6e4 === 0) {
10641
+ return `${timeoutMs / 6e4}m`;
10642
+ }
10643
+ if (timeoutMs % 1e3 === 0) {
10644
+ return `${timeoutMs / 1e3}s`;
10645
+ }
10646
+ return `${timeoutMs}ms`;
10647
+ }
10648
+ function uniqueNormalizedWindows(values) {
10649
+ const seen = /* @__PURE__ */ new Set();
10650
+ const result = [];
10651
+ for (const value of values) {
10652
+ const normalized = normalizeUsageWindow(value);
10653
+ if (!normalized || seen.has(normalized)) {
10654
+ continue;
10655
+ }
10656
+ seen.add(normalized);
10657
+ result.push(normalized);
10658
+ }
10659
+ return result;
10660
+ }
10661
+ function formatUsageTargetLabel(targets) {
10662
+ return buildSupportedTargetLabel(targets);
10663
+ }
10664
+ function formatSupportedUsageTargetsMessage(targets) {
10665
+ const supportedUsageTargets = formatUsageTargetLabel(targets);
10666
+ return supportedUsageTargets ? `Supported usage targets: ${supportedUsageTargets}.` : "No active usage-capable targets are enabled by the current target configuration.";
10667
+ }
10668
+ function getUsageCommand(target) {
10669
+ return target.usage?.launch?.command;
10670
+ }
10671
+ async function checkUsageCommandAvailability(target) {
10672
+ const command = getUsageCommand(target)?.trim();
10673
+ if (!command) {
10674
+ return {
10675
+ status: "available",
10676
+ warnings: []
10677
+ };
10678
+ }
10679
+ const check = await checkCliOnPath(command, {
10680
+ validateCandidate: target.id === "codex" && command === "codex" ? validateCodexCommand : void 0
10681
+ });
10682
+ if (check.result === "available") {
10683
+ return {
10684
+ status: "available",
10685
+ warnings: [],
10686
+ command,
10687
+ resolvedPath: check.resolvedPath ?? command
10688
+ };
10689
+ }
10690
+ if (check.result === "inconclusive") {
10691
+ return {
10692
+ status: "unavailable",
10693
+ reason: "Usage CLI availability could not be confirmed.",
10694
+ warnings: check.warning ? [check.warning] : []
10695
+ };
10696
+ }
10697
+ return {
10698
+ status: "unavailable",
10699
+ reason: `Usage CLI not found on PATH: ${command}.`,
10700
+ warnings: []
10701
+ };
10702
+ }
10703
+ function validateCodexCommand(candidate) {
10704
+ return new Promise((resolve) => {
10705
+ const child = spawn(candidate, ["--version"], {
10706
+ stdio: "ignore"
10707
+ });
10708
+ let settled = false;
10709
+ let timeout;
10710
+ const finish = (valid) => {
10711
+ if (settled) {
10712
+ return;
10713
+ }
10714
+ settled = true;
10715
+ clearTimeout(timeout);
10716
+ resolve(valid);
10717
+ };
10718
+ timeout = setTimeout(() => {
10719
+ child.kill();
10720
+ finish(false);
10721
+ }, 2e3);
10722
+ child.on("error", () => finish(false));
10723
+ child.on("exit", (code) => finish(code === 0));
10724
+ });
10725
+ }
10726
+ function buildContext(options) {
10727
+ const windows = uniqueNormalizedWindows(options.target.usage?.windows ?? []);
10728
+ const launch = {
10729
+ ...options.target.usage?.launch ?? {},
10730
+ timeoutMs: options.timeoutMs
10731
+ };
10732
+ return {
10733
+ targetId: options.target.id,
10734
+ displayName: options.target.displayName,
10735
+ command: options.command ?? getUsageCommand(options.target),
10736
+ window: options.selectedWindow ?? windows[0] ?? "",
10737
+ windows,
10738
+ now: options.now,
10739
+ repoRoot: options.repoRoot,
10740
+ agentsDir: options.agentsDir,
10741
+ homeDir: options.homeDir,
10742
+ launch,
10743
+ signal: options.signal,
10744
+ debug: {
10745
+ enabled: options.debug,
10746
+ includeRawOutput: options.debug,
10747
+ includeScreenSnapshots: options.debug
10748
+ }
10749
+ };
10750
+ }
10751
+ function filterTargetResult(target, result, selectedWindow) {
10752
+ const normalizedLimits = result.limits.map((limit) => ({
10753
+ ...limit,
10754
+ window: normalizeUsageWindow(limit.window)
10755
+ }));
10756
+ const filteredLimits = selectedWindow == null ? normalizedLimits : normalizedLimits.filter((limit) => limit.window === selectedWindow);
10757
+ const notes = selectedWindow != null && filteredLimits.length === 0 ? [`${target.displayName} reported no usage rows for window "${selectedWindow}".`] : [];
10758
+ return {
10759
+ result: {
10760
+ targetId: result.targetId,
10761
+ displayName: result.displayName,
10762
+ command: result.command,
10763
+ limits: filteredLimits
10764
+ },
10765
+ notes,
10766
+ debug: result.debug ?? []
10767
+ };
10768
+ }
10769
+ function buildError(target, code, message) {
10770
+ return {
10771
+ targetId: target.id,
10772
+ displayName: target.displayName,
10773
+ code,
10774
+ message
10775
+ };
10776
+ }
10777
+ async function extractUsageForTarget(options) {
10778
+ try {
10779
+ const extractor = options.target.usage?.extract;
10780
+ if (!extractor) {
10781
+ return {
10782
+ status: "error",
10783
+ target: options.target,
10784
+ error: buildError(
10785
+ options.target,
10786
+ "usage_extractor_missing",
10787
+ `${options.target.displayName} does not have a usage extractor.`
10788
+ ),
10789
+ debug: []
10790
+ };
10791
+ }
10792
+ const result = await withUsageTimeout((signal) => {
10793
+ const context = buildContext({ ...options, signal });
10794
+ return extractor(context);
10795
+ }, options.timeoutMs);
10796
+ const filtered = filterTargetResult(options.target, result, options.selectedWindow);
10797
+ return {
10798
+ status: "success",
10799
+ target: options.target,
10800
+ result: filtered.result,
10801
+ errors: result.errors ?? [],
10802
+ notes: filtered.notes,
10803
+ debug: filtered.debug
10804
+ };
10805
+ } catch (error) {
10806
+ const message = error instanceof Error ? error.message : String(error);
10807
+ const code = isUsageTimeoutError(error) ? "usage_extraction_timeout" : "usage_extraction_failed";
10808
+ return {
10809
+ status: "error",
10810
+ target: options.target,
10811
+ error: buildError(options.target, code, message),
10812
+ debug: options.debug ? getErrorDebugArtifacts(error) : []
10813
+ };
10814
+ }
10815
+ }
10816
+ function isUsageTimeoutError(error) {
10817
+ if (error instanceof UsageExtractionTimeoutError) {
10818
+ return true;
10819
+ }
10820
+ return Boolean(error && typeof error === "object" && error.timedOut);
10821
+ }
10822
+ function getErrorDebugArtifacts(error) {
10823
+ if (!error || typeof error !== "object") {
10824
+ return [];
10825
+ }
10826
+ const debug = error.debug;
10827
+ if (!Array.isArray(debug)) {
10828
+ return [];
10829
+ }
10830
+ return debug.filter(isDebugArtifact);
10831
+ }
10832
+ function isDebugArtifact(value) {
10833
+ if (!value || typeof value !== "object") {
10834
+ return false;
10835
+ }
10836
+ const artifact = value;
10837
+ return (artifact.type === "raw-output" || artifact.type === "screen-snapshot") && typeof artifact.label === "string";
10838
+ }
10839
+ function withUsageTimeout(run, timeoutMs) {
10840
+ return new Promise((resolve, reject) => {
10841
+ const controller = new AbortController();
10842
+ let settled = false;
10843
+ let timeoutFired = false;
10844
+ let timeout;
10845
+ const settleResolve = (value) => {
10846
+ if (settled) {
10847
+ return;
10848
+ }
10849
+ settled = true;
10850
+ clearTimeout(timeout);
10851
+ resolve(value);
10852
+ };
10853
+ const settleReject = (error) => {
10854
+ if (settled) {
10855
+ return;
10856
+ }
10857
+ settled = true;
10858
+ clearTimeout(timeout);
10859
+ reject(error);
10860
+ };
10861
+ timeout = setTimeout(() => {
10862
+ timeoutFired = true;
10863
+ const error = new UsageExtractionTimeoutError(timeoutMs);
10864
+ controller.abort(error);
10865
+ setImmediate(() => {
10866
+ settleReject(error);
10867
+ });
10868
+ }, timeoutMs);
10869
+ let promise;
10870
+ try {
10871
+ promise = run(controller.signal);
10872
+ } catch (error) {
10873
+ settleReject(error);
10874
+ return;
10875
+ }
10876
+ promise.then(
10877
+ (value) => {
10878
+ if (timeoutFired) {
10879
+ return;
10880
+ }
10881
+ settleResolve(value);
10882
+ },
10883
+ (error) => {
10884
+ if (timeoutFired && !isUsageTimeoutError(error)) {
10885
+ return;
10886
+ }
10887
+ settleReject(error);
10888
+ }
10889
+ );
10890
+ });
10891
+ }
10892
+ function buildEnvelope(options) {
10893
+ const envelope = {
10894
+ schemaVersion: 1,
10895
+ generatedAt: options.generatedAt,
10896
+ targets: options.targets,
10897
+ errors: options.errors,
10898
+ notes: options.notes
10899
+ };
10900
+ if (options.debug && options.debugArtifacts.length > 0) {
10901
+ envelope.debug = options.debugArtifacts;
10902
+ }
10903
+ return envelope;
10904
+ }
10905
+ function buildCommandError(code, message) {
10906
+ return {
10907
+ targetId: "usage",
10908
+ displayName: "Usage command",
10909
+ code,
10910
+ message
10911
+ };
10912
+ }
10913
+ function printError(options) {
10914
+ if (options.json) {
10915
+ const error = options.target ? buildError(options.target, options.code, options.message) : buildCommandError(options.code, options.message);
10916
+ console.log(
10917
+ JSON.stringify(
10918
+ buildEnvelope({
10919
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
10920
+ targets: [],
10921
+ errors: [error],
10922
+ notes: [],
10923
+ debug: false,
10924
+ debugArtifacts: []
10925
+ }),
10926
+ null,
10927
+ 2
10928
+ )
10929
+ );
10930
+ } else {
10931
+ console.error(`Error: ${options.message}`);
10932
+ }
10933
+ process.exit(options.exitCode);
10934
+ }
10935
+ function percentText(value) {
10936
+ return value == null ? "unknown" : `${Math.round(value)}%`;
10937
+ }
10938
+ function usageBar(percentUsed) {
10939
+ if (percentUsed == null) {
10940
+ return `[${"?".repeat(USAGE_BAR_WIDTH)}]`;
10941
+ }
10942
+ const clamped = Math.max(0, Math.min(100, percentUsed));
10943
+ const filled = clamped === 0 ? 0 : Math.max(1, Math.round(clamped / 100 * USAGE_BAR_WIDTH));
10944
+ return `[${"#".repeat(filled)}${"-".repeat(USAGE_BAR_WIDTH - filled)}]`;
10945
+ }
10946
+ function formatResetValue(limit, now) {
10947
+ const resetAt = parseDate(limit.resetAt);
10948
+ if (resetAt != null) {
10949
+ return formatLocalResetAt(resetAt, now);
10950
+ }
10951
+ const value = limit.resetText ?? "-";
10952
+ return value === "-" ? value : value.replace(/^resets\s+/i, "");
10953
+ }
10954
+ function parseDate(value) {
10955
+ if (!value) {
10956
+ return null;
10957
+ }
10958
+ const date = new Date(value);
10959
+ return Number.isNaN(date.getTime()) ? null : date;
10960
+ }
10961
+ function formatLocalResetAt(resetAt, now) {
10962
+ const relative = formatResetDuration(resetAt.getTime() - now.getTime()).padEnd(
10963
+ RELATIVE_RESET_WIDTH
10964
+ );
10965
+ return `${relative} (${formatResetExact(resetAt, now)})`;
10966
+ }
10967
+ function formatResetDuration(milliseconds) {
10968
+ if (milliseconds <= 0) {
10969
+ return "now";
10970
+ }
10971
+ const totalMinutes = Math.max(1, Math.ceil(milliseconds / MS_PER_MINUTE));
10972
+ if (totalMinutes < 60) {
10973
+ return `${totalMinutes}m`;
10974
+ }
10975
+ if (totalMinutes < DETAILED_RESET_THRESHOLD_MINUTES) {
10976
+ const hours = Math.floor(totalMinutes / 60);
10977
+ const minutes = totalMinutes % 60;
10978
+ return minutes === 0 ? `${hours}h` : `${hours}h${minutes}m`;
10979
+ }
10980
+ if (milliseconds >= MS_PER_DAY) {
10981
+ return `${Math.ceil(milliseconds / MS_PER_DAY)}d`;
10982
+ }
10983
+ return `${Math.round(milliseconds / MS_PER_HOUR)}h`;
10984
+ }
10985
+ function formatResetExact(resetAt, now) {
10986
+ const dayDifference = localDayIndex(resetAt) - localDayIndex(now);
10987
+ if (dayDifference >= 0 && dayDifference <= 7) {
10988
+ return RESET_WEEKDAY_TIME_FORMATTER.format(resetAt);
10989
+ }
10990
+ return RESET_DATE_TIME_FORMATTER.format(resetAt);
10991
+ }
10992
+ function localDayIndex(date) {
10993
+ return Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / MS_PER_DAY;
10994
+ }
10995
+ function formatLimitLabel(limit) {
10996
+ return limit.label ?? limit.modelLabel ?? limit.modelId ?? formatWindowLabel(limit.window);
10997
+ }
10998
+ function formatLimitLabels(limits) {
10999
+ const baseLabels = limits.map(formatLimitLabel);
11000
+ const duplicateLabels = new Set(
11001
+ baseLabels.filter((label, index) => baseLabels.indexOf(label) !== index)
11002
+ );
11003
+ return limits.map((limit, index) => {
11004
+ const baseLabel = baseLabels[index] ?? formatLimitLabel(limit);
11005
+ if (!duplicateLabels.has(baseLabel) || !limit.scope) {
11006
+ return baseLabel;
11007
+ }
11008
+ if (limit.scope === "main") {
11009
+ return baseLabel;
11010
+ }
11011
+ return `${formatScopeLabel(limit.scope)} ${baseLabel}`;
11012
+ });
11013
+ }
11014
+ function formatScopeLabel(scope) {
11015
+ return scope.replace(/[_-]+/g, " ").split(/\s+/).filter(Boolean).map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(" ");
11016
+ }
11017
+ function formatWindowLabel(window) {
11018
+ if (window === "hourly") {
11019
+ return "5h";
11020
+ }
11021
+ if (window === "weekly") {
11022
+ return "Weekly";
11023
+ }
11024
+ return window;
11025
+ }
11026
+ function formatUsageAgentName(targetId, displayName) {
11027
+ return targetId === "codex" && displayName === "OpenAI Codex" ? "Codex CLI" : displayName;
11028
+ }
11029
+ function formatUsageTable(envelope, sortKey) {
11030
+ const useColor = shouldUseColor();
11031
+ const generatedAt = parseDate(envelope.generatedAt) ?? /* @__PURE__ */ new Date();
11032
+ const rows = [];
11033
+ for (const target of envelope.targets) {
11034
+ const limitLabels = formatLimitLabels(target.limits);
11035
+ const agentName = formatUsageAgentName(target.targetId, target.displayName);
11036
+ target.limits.forEach((limit, index) => {
11037
+ rows.push({
11038
+ status: "ok",
11039
+ agent: sortKey == null && index > 0 ? "" : agentName,
11040
+ limitLabel: limitLabels[index] ?? formatLimitLabel(limit),
11041
+ reset: formatResetValue(limit, generatedAt),
11042
+ limit
11043
+ });
11044
+ });
11045
+ }
11046
+ for (const error of envelope.errors) {
11047
+ rows.push({
11048
+ status: "error",
11049
+ agent: formatUsageAgentName(error.targetId, error.displayName),
11050
+ limitLabel: "error",
11051
+ message: error.message
11052
+ });
11053
+ }
11054
+ const rendered = [renderUsageTable(sortUsageRows(rows, sortKey), useColor)];
11055
+ if (envelope.notes.length > 0) {
11056
+ rendered.push("", ...envelope.notes.map((note) => color(`Note: ${note}`, "dim", useColor)));
11057
+ }
11058
+ return rendered.join("\n");
11059
+ }
11060
+ function sortUsageRows(rows, sortKey) {
11061
+ if (sortKey == null) {
11062
+ return rows;
11063
+ }
11064
+ return rows.map((row, index) => ({ row, index })).sort((left, right) => {
11065
+ const leftValue = usageSortValue(left.row, sortKey);
11066
+ const rightValue = usageSortValue(right.row, sortKey);
11067
+ if (leftValue == null && rightValue == null) {
11068
+ return left.index - right.index;
11069
+ }
11070
+ if (leftValue == null) {
11071
+ return 1;
11072
+ }
11073
+ if (rightValue == null) {
11074
+ return -1;
11075
+ }
11076
+ if (leftValue !== rightValue) {
11077
+ return leftValue - rightValue;
11078
+ }
11079
+ return left.index - right.index;
11080
+ }).map(({ row }) => row);
11081
+ }
11082
+ function usageSortValue(row, sortKey) {
11083
+ if (row.status === "error") {
11084
+ return null;
11085
+ }
11086
+ if (sortKey === "left") {
11087
+ return row.limit.percentRemaining;
11088
+ }
11089
+ const resetAt = parseDate(row.limit.resetAt);
11090
+ return resetAt?.getTime() ?? null;
11091
+ }
11092
+ function renderUsageTable(rows, useColor) {
11093
+ const widths = {
11094
+ agent: maxWidth(
11095
+ "Agent",
11096
+ rows.map((row) => row.agent)
11097
+ ),
11098
+ limit: maxWidth(
11099
+ "Limit",
11100
+ rows.map((row) => row.limitLabel)
11101
+ ),
11102
+ usage: maxWidth("Usage", rows.map(usageCellText)),
11103
+ left: maxWidth("Left", rows.map(leftCellText)),
11104
+ reset: maxWidth("Reset", rows.map(resetCellText))
11105
+ };
11106
+ const headerLine = [
11107
+ pad("Agent", widths.agent),
11108
+ pad("Limit", widths.limit),
11109
+ pad("Left", widths.left),
11110
+ pad("Usage", widths.usage),
11111
+ pad("Reset", widths.reset)
11112
+ ].join(" ").trimEnd();
11113
+ const separatorLine = [
11114
+ "-".repeat(widths.agent),
11115
+ "-".repeat(widths.limit),
11116
+ "-".repeat(widths.left),
11117
+ "-".repeat(widths.usage),
11118
+ "-".repeat(widths.reset)
11119
+ ].join(" ").trimEnd();
11120
+ return [
11121
+ color(headerLine, "bold", useColor),
11122
+ color(separatorLine, "dim", useColor),
11123
+ ...rows.map((row) => renderUsageRow(row, widths, useColor))
11124
+ ].join("\n");
11125
+ }
11126
+ function renderUsageRow(row, widths, useColor) {
11127
+ return [
11128
+ pad(row.agent, widths.agent),
11129
+ pad(row.limitLabel, widths.limit),
11130
+ renderLeftCell(row, widths.left, useColor),
11131
+ renderUsageCell(row, widths.usage, useColor),
11132
+ renderResetCell(row, widths.reset, useColor)
11133
+ ].join(" ").trimEnd();
11134
+ }
11135
+ function usageCellText(row) {
11136
+ if (row.status === "error") {
11137
+ return "failed";
11138
+ }
11139
+ return `${usageBar(row.limit.percentUsed)} ${percentText(row.limit.percentUsed).padStart(4)} used`;
11140
+ }
11141
+ function leftCellText(row) {
11142
+ if (row.status === "error") {
11143
+ return "-";
11144
+ }
11145
+ return percentText(row.limit.percentRemaining);
11146
+ }
11147
+ function resetCellText(row) {
11148
+ if (row.status === "error") {
11149
+ return `Error: ${row.message}`;
11150
+ }
11151
+ return row.reset;
11152
+ }
11153
+ function renderUsageCell(row, width, useColor) {
11154
+ const text = usageCellText(row);
11155
+ const padding = " ".repeat(Math.max(0, width - text.length));
11156
+ if (row.status === "error") {
11157
+ return `${color(text, "red", useColor)}${padding}`;
11158
+ }
11159
+ const severity = usageSeverity(row.limit.percentUsed);
11160
+ const used = percentText(row.limit.percentUsed).padStart(4);
11161
+ return `${color(usageBar(row.limit.percentUsed), severity, useColor)} ${color(
11162
+ used,
11163
+ severity,
11164
+ useColor
11165
+ )} used${padding}`;
11166
+ }
11167
+ function renderLeftCell(row, width, useColor) {
11168
+ const text = leftCellText(row);
11169
+ const padding = " ".repeat(Math.max(0, width - text.length));
11170
+ if (row.status === "error") {
11171
+ return `${color(text, "gray", useColor)}${padding}`;
11172
+ }
11173
+ return `${color(text, remainingSeverity(row.limit.percentRemaining), useColor)}${padding}`;
11174
+ }
11175
+ function renderResetCell(row, width, useColor) {
11176
+ const text = resetCellText(row);
11177
+ const padding = " ".repeat(Math.max(0, width - text.length));
11178
+ const style = row.status === "error" ? "red" : "gray";
11179
+ return `${color(text, style, useColor)}${padding}`;
11180
+ }
11181
+ function usageSeverity(percentUsed) {
11182
+ if (percentUsed == null) {
11183
+ return "gray";
11184
+ }
11185
+ if (percentUsed >= 95) {
11186
+ return "red";
11187
+ }
11188
+ if (percentUsed >= 80) {
11189
+ return "orange";
11190
+ }
11191
+ if (percentUsed >= 60) {
11192
+ return "yellow";
11193
+ }
11194
+ return "green";
11195
+ }
11196
+ function remainingSeverity(percentRemaining) {
11197
+ if (percentRemaining == null) {
11198
+ return "gray";
11199
+ }
11200
+ if (percentRemaining <= 5) {
11201
+ return "red";
11202
+ }
11203
+ if (percentRemaining <= 20) {
11204
+ return "orange";
11205
+ }
11206
+ if (percentRemaining <= 40) {
11207
+ return "yellow";
11208
+ }
11209
+ return "green";
11210
+ }
11211
+ function maxWidth(header, values) {
11212
+ return Math.max(header.length, ...values.map((value) => value.length));
11213
+ }
11214
+ function pad(value, width) {
11215
+ return value.padEnd(width);
11216
+ }
11217
+ function color(value, style, useColor) {
11218
+ if (!useColor) {
11219
+ return value;
11220
+ }
11221
+ return `${ANSI[style]}${value}${ANSI.reset}`;
11222
+ }
11223
+ function shouldUseColor() {
11224
+ if (process.env.FORCE_COLOR != null && process.env.FORCE_COLOR !== "0") {
11225
+ return true;
11226
+ }
11227
+ if (process.env.NO_COLOR != null) {
11228
+ return false;
11229
+ }
11230
+ return Boolean(process.stdout.isTTY);
11231
+ }
11232
+ async function runUsageCommand(argv) {
11233
+ const positionalTargets = argv.targets ?? [];
11234
+ const onlyTargets = parseList(argv.only);
11235
+ const jsonOutput = Boolean(argv.json || argv.debug);
11236
+ const debugOutput = Boolean(argv.debug);
11237
+ const selectedWindow = normalizeOptionalWindow(argv.window);
11238
+ const sortKey = normalizeOptionalSort(argv.sort);
11239
+ const cliTimeoutMs = parseTimeoutMs(argv.timeout);
11240
+ if (selectedWindow === "") {
11241
+ printError({
11242
+ json: jsonOutput,
11243
+ code: "invalid_window",
11244
+ message: "--window must be a non-empty value.",
11245
+ exitCode: 2
11246
+ });
11247
+ return null;
11248
+ }
11249
+ if (sortKey === "") {
11250
+ printError({
11251
+ json: jsonOutput,
11252
+ code: "invalid_sort",
11253
+ message: "--sort must be one of: reset, left.",
11254
+ exitCode: 2
11255
+ });
11256
+ return null;
11257
+ }
11258
+ if (sortKey != null && jsonOutput) {
11259
+ printError({
11260
+ json: jsonOutput,
11261
+ code: "sort_json_unsupported",
11262
+ message: "--sort is only supported for the human table output.",
11263
+ exitCode: 2
11264
+ });
11265
+ return null;
11266
+ }
11267
+ if (cliTimeoutMs === null) {
11268
+ printError({
11269
+ json: jsonOutput,
11270
+ code: "invalid_timeout",
11271
+ message: "--timeout must be a positive duration. Use seconds by default, or units like 500ms, 5s, or 1m.",
11272
+ exitCode: 2
11273
+ });
11274
+ return null;
11275
+ }
11276
+ if (argv.only != null && onlyTargets.length === 0) {
11277
+ printError({
11278
+ json: jsonOutput,
11279
+ code: "invalid_only",
11280
+ message: "--only must include at least one target.",
11281
+ exitCode: 2
11282
+ });
11283
+ return null;
11284
+ }
11285
+ if (positionalTargets.length > 1) {
11286
+ printError({
11287
+ json: jsonOutput,
11288
+ code: "too_many_targets",
11289
+ message: "omniagent usage accepts at most one target.",
11290
+ exitCode: 2
11291
+ });
11292
+ return null;
11293
+ }
11294
+ if (positionalTargets.length > 0 && onlyTargets.length > 0) {
11295
+ printError({
11296
+ json: jsonOutput,
11297
+ code: "conflicting_target_selection",
11298
+ message: "Use either a positional target or --only, not both.",
11299
+ exitCode: 2
11300
+ });
11301
+ return null;
11302
+ }
11303
+ const startDir = process.cwd();
11304
+ const repoRoot = await findRepoRoot(startDir) ?? startDir;
11305
+ const agentsDirResolution = resolveAgentsDir(repoRoot, argv.agentsDir);
11306
+ if (agentsDirResolution.source === "override") {
11307
+ const validation2 = await validateAgentsDir(repoRoot, argv.agentsDir, { requireWrite: false });
11308
+ if (validation2.validationStatus !== "valid") {
11309
+ printError({
11310
+ json: jsonOutput,
11311
+ code: "invalid_agents_dir",
11312
+ message: validation2.errorMessage,
11313
+ exitCode: 1
11314
+ });
11315
+ return null;
11316
+ }
11317
+ }
11318
+ const agentsDir = agentsDirResolution.resolvedPath;
11319
+ const homeDir = os.homedir();
11320
+ const { config } = await loadTargetConfig({ repoRoot, agentsDir });
11321
+ const validation = validateTargetConfig({ config, builtIns: BUILTIN_TARGETS });
11322
+ if (!validation.valid) {
11323
+ printError({
11324
+ json: jsonOutput,
11325
+ code: "invalid_target_config",
11326
+ message: `Invalid target configuration:
11327
+ - ${validation.errors.join("\n- ")}`,
11328
+ exitCode: 1
11329
+ });
11330
+ return null;
11331
+ }
11332
+ const resolved = resolveTargets({ config: validation.config, builtIns: BUILTIN_TARGETS });
11333
+ const targetResolver = createTargetNameResolver(resolved.targets);
11334
+ const usageCapableTargets = resolved.targets.filter((target) => target.usage);
11335
+ const supportedUsageTargetsMessage = formatSupportedUsageTargetsMessage(usageCapableTargets);
11336
+ const usingOnlySelection = onlyTargets.length > 0;
11337
+ const explicitTargetNames = positionalTargets[0] ? [positionalTargets[0]] : onlyTargets;
11338
+ let selectedTargets;
11339
+ const resolvedUsageCommands = /* @__PURE__ */ new Map();
11340
+ if (explicitTargetNames.length > 0) {
11341
+ const selectedIds = [];
11342
+ const unknownTargetNames = [];
11343
+ for (const targetName of explicitTargetNames) {
11344
+ const resolvedName = targetResolver.resolveTargetName(targetName);
11345
+ if (!resolvedName) {
11346
+ unknownTargetNames.push(targetName);
11347
+ continue;
11348
+ }
11349
+ if (!selectedIds.includes(resolvedName)) {
11350
+ selectedIds.push(resolvedName);
11351
+ }
11352
+ }
11353
+ if (unknownTargetNames.length > 0) {
11354
+ const unknownLabel = unknownTargetNames.join(", ");
11355
+ const message = usingOnlySelection ? `Unknown target name(s): ${unknownLabel}. ${supportedUsageTargetsMessage}` : `Unknown target: ${unknownLabel}. ${supportedUsageTargetsMessage}`;
11356
+ printError({
11357
+ json: jsonOutput,
11358
+ code: "unknown_target",
11359
+ message,
11360
+ exitCode: 2
11361
+ });
11362
+ return null;
11363
+ }
11364
+ const requestedTargets = selectedIds.flatMap((targetId) => {
11365
+ const target = resolved.byId.get(targetId.toLowerCase());
11366
+ return target ? [target] : [];
11367
+ });
11368
+ const unsupportedTargets = requestedTargets.filter((target) => !target.usage);
11369
+ if (unsupportedTargets.length > 0) {
11370
+ const unsupportedLabel = unsupportedTargets.map((target) => target.displayName).join(", ");
11371
+ printError({
11372
+ json: jsonOutput,
11373
+ code: "usage_unsupported",
11374
+ message: `${unsupportedLabel} does not support usage extraction. ${supportedUsageTargetsMessage}`,
11375
+ exitCode: 2,
11376
+ target: unsupportedTargets.length === 1 ? unsupportedTargets[0] : void 0
11377
+ });
11378
+ return null;
11379
+ }
11380
+ const requestedUsageIds = new Set(selectedIds);
11381
+ selectedTargets = usageCapableTargets.filter((target) => requestedUsageIds.has(target.id));
11382
+ const availabilityResults = await Promise.all(
11383
+ selectedTargets.map(async (target) => ({
11384
+ target,
11385
+ availability: await checkUsageCommandAvailability(target)
11386
+ }))
11387
+ );
11388
+ const unavailableResults = availabilityResults.filter(
11389
+ ({ availability }) => availability.status !== "available"
11390
+ );
11391
+ if (unavailableResults.length > 0) {
11392
+ if (jsonOutput) {
11393
+ const now2 = /* @__PURE__ */ new Date();
11394
+ const envelope2 = buildEnvelope({
11395
+ generatedAt: now2.toISOString(),
11396
+ targets: [],
11397
+ errors: unavailableResults.map(
11398
+ ({ target, availability }) => buildError(target, "cli_unavailable", availability.reason ?? "CLI not found on PATH.")
11399
+ ),
11400
+ notes: [],
11401
+ debug: debugOutput,
11402
+ debugArtifacts: []
11403
+ });
11404
+ console.log(JSON.stringify(envelope2, null, 2));
11405
+ } else if (unavailableResults.length === 1) {
11406
+ const unavailableResult = unavailableResults[0];
11407
+ if (unavailableResult) {
11408
+ const { target, availability } = unavailableResult;
11409
+ const message = availability.reason ?? "CLI not found on PATH.";
11410
+ console.error(
11411
+ `Error: ${target.displayName} usage extraction requires its CLI. ${message}`
11412
+ );
11413
+ }
11414
+ } else {
11415
+ console.error(
11416
+ [
11417
+ "Error: Some requested usage CLIs are unavailable:",
11418
+ ...unavailableResults.map(({ target, availability }) => {
11419
+ const message = availability.reason ?? "CLI not found on PATH.";
11420
+ return `- ${target.displayName}: ${message}`;
11421
+ })
11422
+ ].join("\n")
11423
+ );
11424
+ }
11425
+ process.exit(1);
11426
+ return null;
11427
+ }
11428
+ for (const { target, availability } of availabilityResults) {
11429
+ const resolvedCommand = availability.resolvedPath ?? availability.command;
11430
+ if (resolvedCommand) {
11431
+ resolvedUsageCommands.set(target.id, resolvedCommand);
11432
+ }
11433
+ }
11434
+ } else {
11435
+ const availabilityResults = await Promise.all(
11436
+ usageCapableTargets.map(async (target) => ({
11437
+ target,
11438
+ availability: await checkUsageCommandAvailability(target)
11439
+ }))
11440
+ );
11441
+ selectedTargets = [];
11442
+ for (const { target, availability } of availabilityResults) {
11443
+ if (availability.status !== "available") {
11444
+ continue;
11445
+ }
11446
+ selectedTargets.push(target);
11447
+ const resolvedCommand = availability.resolvedPath ?? availability.command;
11448
+ if (resolvedCommand) {
11449
+ resolvedUsageCommands.set(target.id, resolvedCommand);
11450
+ }
11451
+ }
11452
+ if (selectedTargets.length === 0) {
11453
+ const supportedUsageTargets = formatUsageTargetLabel(usageCapableTargets);
11454
+ const message = supportedUsageTargets ? `No installed active usage-capable agents were found. Install one of: ${supportedUsageTargets}.` : "No active usage-capable targets are enabled by the current target configuration.";
11455
+ if (jsonOutput) {
11456
+ const now2 = /* @__PURE__ */ new Date();
11457
+ const envelope2 = buildEnvelope({
11458
+ generatedAt: now2.toISOString(),
11459
+ targets: [],
11460
+ errors: [],
11461
+ notes: [message],
11462
+ debug: debugOutput,
11463
+ debugArtifacts: []
11464
+ });
11465
+ console.log(JSON.stringify(envelope2, null, 2));
11466
+ } else {
11467
+ console.log(message);
11468
+ }
11469
+ return {
11470
+ envelope: buildEnvelope({
11471
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
11472
+ targets: [],
11473
+ errors: [],
11474
+ notes: [message],
11475
+ debug: debugOutput,
11476
+ debugArtifacts: []
11477
+ }),
11478
+ exitCode: 0,
11479
+ selectedTargets
11480
+ };
11481
+ }
11482
+ }
11483
+ const now = /* @__PURE__ */ new Date();
11484
+ const outcomes = await Promise.all(
11485
+ selectedTargets.map(
11486
+ (target) => extractUsageForTarget({
11487
+ target,
11488
+ repoRoot,
11489
+ agentsDir,
11490
+ homeDir,
11491
+ selectedWindow,
11492
+ timeoutMs: resolveTargetTimeoutMs(target, cliTimeoutMs),
11493
+ debug: debugOutput,
11494
+ now,
11495
+ command: resolvedUsageCommands.get(target.id)
11496
+ })
11497
+ )
11498
+ );
11499
+ const targets = [];
11500
+ const errors = [];
11501
+ const notes = [];
11502
+ const debugArtifacts = [];
11503
+ for (const outcome of outcomes) {
11504
+ if (outcome.status === "success") {
11505
+ targets.push(outcome.result);
11506
+ errors.push(...outcome.errors);
11507
+ notes.push(...outcome.notes);
11508
+ debugArtifacts.push(
11509
+ ...outcome.debug.map((artifact) => ({
11510
+ ...artifact,
11511
+ targetId: outcome.target.id,
11512
+ displayName: outcome.target.displayName
11513
+ }))
11514
+ );
11515
+ } else {
11516
+ errors.push(outcome.error);
11517
+ debugArtifacts.push(
11518
+ ...outcome.debug.map((artifact) => ({
11519
+ ...artifact,
11520
+ targetId: outcome.target.id,
11521
+ displayName: outcome.target.displayName
11522
+ }))
11523
+ );
11524
+ }
11525
+ }
11526
+ const envelope = buildEnvelope({
11527
+ generatedAt: now.toISOString(),
11528
+ targets,
11529
+ errors,
11530
+ notes,
11531
+ debug: debugOutput,
11532
+ debugArtifacts
11533
+ });
11534
+ const exitCode = errors.length > 0 ? 1 : 0;
11535
+ if (jsonOutput) {
11536
+ console.log(JSON.stringify(envelope, null, 2));
11537
+ } else {
11538
+ console.log(formatUsageTable(envelope, sortKey));
11539
+ }
11540
+ if (exitCode !== 0) {
11541
+ process.exit(exitCode);
11542
+ }
11543
+ return { envelope, exitCode, selectedTargets };
11544
+ }
11545
+ const usageCommand = {
11546
+ command: "usage [targets..]",
11547
+ describe: "Report usage limits for installed agent CLIs",
11548
+ builder: (yargsInstance) => yargsInstance.usage(
11549
+ "omniagent usage [target] [--only <targets>] [--sort <key>] [--window <window>] [--timeout <seconds>] [--agentsDir <path>] [--json] [--debug]"
11550
+ ).positional("targets", {
11551
+ type: "string",
11552
+ array: true,
11553
+ describe: "Optional target id or alias."
11554
+ }).option("window", {
11555
+ type: "string",
11556
+ describe: "Filter usage rows by window (hourly, weekly, 5h, or a custom window)."
11557
+ }).option("only", {
11558
+ type: "string",
11559
+ describe: "Comma-separated target ids or aliases to report usage for."
11560
+ }).option("sort", {
11561
+ type: "string",
11562
+ describe: "Globally sort table rows by reset or left."
11563
+ }).option("timeout", {
11564
+ type: "string",
11565
+ describe: "Per-agent extraction timeout. Bare numbers are seconds; units include ms, s, and m."
11566
+ }).option("agentsDir", {
11567
+ type: "string",
11568
+ describe: "Override the agents directory (relative paths resolve from the project root, or the current directory outside a repo)",
11569
+ defaultDescription: DEFAULT_AGENTS_DIR,
11570
+ coerce: (value) => {
11571
+ if (typeof value !== "string") {
11572
+ return value;
11573
+ }
11574
+ const trimmed = value.trim();
11575
+ return trimmed.length > 0 ? trimmed : void 0;
11576
+ }
11577
+ }).option("json", {
11578
+ type: "boolean",
11579
+ describe: "Print a stable JSON envelope."
11580
+ }).option("debug", {
11581
+ type: "boolean",
11582
+ describe: "Print JSON and include extractor debug artifacts when available."
11583
+ }).epilog(
11584
+ "Usage extraction may launch agent TUIs and may incur cost if an agent reads repo context on startup. omniagent uses cheap/minimal launch settings where possible."
11585
+ ),
11586
+ handler: async (argv) => {
11587
+ await runUsageCommand(argv);
11588
+ }
11589
+ };
10212
11590
  const EXIT_CODES = {
10213
11591
  success: 0,
10214
11592
  "execution-error": 1,
@@ -10879,7 +12257,7 @@ function resolveVersion() {
10879
12257
  return "0.0.0";
10880
12258
  }
10881
12259
  const VERSION = resolveVersion();
10882
- const KNOWN_COMMANDS = /* @__PURE__ */ new Set(["hello", "greet", "echo", "sync", "dev", "profiles"]);
12260
+ const KNOWN_COMMANDS = /* @__PURE__ */ new Set(["hello", "greet", "echo", "sync", "dev", "profiles", "usage"]);
10883
12261
  const SHIM_CAPABILITIES = [
10884
12262
  "Capabilities by agent:",
10885
12263
  " codex: approval, sandbox, output, model, web",
@@ -10929,7 +12307,7 @@ function runCli(argv = process.argv, options = {}) {
10929
12307
  console.error(formatError(message, args));
10930
12308
  const exitCode = isCommandInvocation(args) ? 1 : 2;
10931
12309
  process.exit(exitCode);
10932
- }).command(helloCommand).command(greetCommand).command(echoCommand).command(syncCommand).command(devCommand).command(profilesCommand).command(
12310
+ }).command(helloCommand).command(greetCommand).command(echoCommand).command(syncCommand).command(devCommand).command(profilesCommand).command(usageCommand).command(
10933
12311
  "$0",
10934
12312
  "omniagent CLI",
10935
12313
  (yargsInstance) => yargsInstance.usage("omniagent [flags] --agent <target-id> [-- <agent flags>]").example("omniagent --agent codex", "Start an interactive session (default mode).").example('omniagent -p "Summarize the repo" --agent codex', "Run a one-shot prompt.").example("omniagent --agent codex -- --some-flag", "Pass through agent-specific flags.").option("prompt", {
@@ -10989,5 +12367,11 @@ if (!entry) {
10989
12367
  }
10990
12368
  }
10991
12369
  export {
12370
+ compactLines as a,
12371
+ parsePercentRemaining as b,
12372
+ cleanControlOutput as c,
12373
+ parseResetText as d,
12374
+ makeUsageLimit as m,
12375
+ parsePercentUsed as p,
10992
12376
  runCli
10993
12377
  };