github-router 0.3.314 → 0.3.315

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.
Files changed (102) hide show
  1. package/dist/{aic-ledger-Cx_Ubon3.js → aic-ledger-BS47yb_C.js} +66 -6
  2. package/dist/aic-ledger-BS47yb_C.js.map +1 -0
  3. package/dist/{attribution-settings-Btc5EZTj.js → attribution-settings-DXmmyuEE.js} +8 -8
  4. package/dist/{attribution-settings-Btc5EZTj.js.map → attribution-settings-DXmmyuEE.js.map} +1 -1
  5. package/dist/{auth-O_UG1ABO.js → auth-D8t41c5D.js} +3 -3
  6. package/dist/{auth-O_UG1ABO.js.map → auth-D8t41c5D.js.map} +1 -1
  7. package/dist/browser-ext/manifest.json +1 -1
  8. package/dist/{check-usage-dicJOWML.js → check-usage-BpBKOjGZ.js} +4 -4
  9. package/dist/{check-usage-dicJOWML.js.map → check-usage-BpBKOjGZ.js.map} +1 -1
  10. package/dist/{claude-D0vuAqTV.js → claude-Cmmu3-cL.js} +30 -24
  11. package/dist/claude-Cmmu3-cL.js.map +1 -0
  12. package/dist/{codex-BNm9D0iJ.js → codex-DqTKKoEx.js} +5 -5
  13. package/dist/{codex-BNm9D0iJ.js.map → codex-DqTKKoEx.js.map} +1 -1
  14. package/dist/copilot-discount-IbXR5A28.js +220 -0
  15. package/dist/copilot-discount-IbXR5A28.js.map +1 -0
  16. package/dist/{debug-2PgImRnr.js → debug-CA7pg8V9.js} +2 -2
  17. package/dist/{debug-2PgImRnr.js.map → debug-CA7pg8V9.js.map} +1 -1
  18. package/dist/engine-b9XaKLp0.js +2 -0
  19. package/dist/{exec-y8C_MU8A.js → exec-COUT4QsS.js} +2 -2
  20. package/dist/{exec-y8C_MU8A.js.map → exec-COUT4QsS.js.map} +1 -1
  21. package/dist/{gate-discovery-CO2njGBB.js → gate-discovery-CPk6Z8yc.js} +6 -6
  22. package/dist/{gate-discovery-CO2njGBB.js.map → gate-discovery-CPk6Z8yc.js.map} +1 -1
  23. package/dist/{get-copilot-usage-Cjy8TiFf.js → get-copilot-usage-FSQE8szQ.js} +2 -2
  24. package/dist/{get-copilot-usage-Cjy8TiFf.js.map → get-copilot-usage-FSQE8szQ.js.map} +1 -1
  25. package/dist/hooks.mjs +489 -28
  26. package/dist/hooks.sha256 +1 -1
  27. package/dist/internal-aic-status-BN0xRLSF.js +432 -0
  28. package/dist/internal-aic-status-BN0xRLSF.js.map +1 -0
  29. package/dist/{internal-artifact-open-CxeIfsW_.js → internal-artifact-open-B3stUu74.js} +2 -2
  30. package/dist/{internal-artifact-open-CxeIfsW_.js.map → internal-artifact-open-B3stUu74.js.map} +1 -1
  31. package/dist/{internal-fast-dispatch-guard-D8YNlnuQ.js → internal-fast-dispatch-guard-2QjUlhqO.js} +1 -1
  32. package/dist/{internal-fast-dispatch-guard-19xQEouu.js → internal-fast-dispatch-guard-CAwYj_Yz.js} +2 -2
  33. package/dist/{internal-fast-dispatch-guard-19xQEouu.js.map → internal-fast-dispatch-guard-CAwYj_Yz.js.map} +1 -1
  34. package/dist/{internal-first-mate-guard-BuweUgjb.js → internal-first-mate-guard-B0rbLb0L.js} +3 -3
  35. package/dist/{internal-first-mate-guard-BuweUgjb.js.map → internal-first-mate-guard-B0rbLb0L.js.map} +1 -1
  36. package/dist/{internal-first-mate-guard-Nf1H0mCK.js → internal-first-mate-guard-VgnHBp0s.js} +1 -1
  37. package/dist/{internal-max-dispatch-guard-_Pp4LltM.js → internal-max-dispatch-guard-BzKsY9ze.js} +1 -1
  38. package/dist/{internal-max-dispatch-guard-DZ7KLWxP.js → internal-max-dispatch-guard-l1NGYHtF.js} +2 -2
  39. package/dist/{internal-max-dispatch-guard-DZ7KLWxP.js.map → internal-max-dispatch-guard-l1NGYHtF.js.map} +1 -1
  40. package/dist/{internal-plan-review-DLduDz41.js → internal-plan-review-QUvNZmMR.js} +3 -3
  41. package/dist/{internal-plan-review-DLduDz41.js.map → internal-plan-review-QUvNZmMR.js.map} +1 -1
  42. package/dist/{internal-prompt-submit-DG94f8E-.js → internal-prompt-submit-fj5ccft_.js} +5 -5
  43. package/dist/{internal-prompt-submit-DG94f8E-.js.map → internal-prompt-submit-fj5ccft_.js.map} +1 -1
  44. package/dist/{internal-session-bind-D6Hpl-84.js → internal-session-bind-0Hmak8PR.js} +2 -2
  45. package/dist/{internal-session-bind-D6Hpl-84.js.map → internal-session-bind-0Hmak8PR.js.map} +1 -1
  46. package/dist/{internal-stop-hook-D56b6iVj.js → internal-stop-hook-BKiKYeda.js} +6 -6
  47. package/dist/{internal-stop-hook-D56b6iVj.js.map → internal-stop-hook-BKiKYeda.js.map} +1 -1
  48. package/dist/{internal-stop-review-DlKzZgFm.js → internal-stop-review-8v2llYDB.js} +2 -2
  49. package/dist/{internal-stop-review-DlKzZgFm.js.map → internal-stop-review-8v2llYDB.js.map} +1 -1
  50. package/dist/{internal-worker-guard-D7sfByyo.js → internal-worker-guard-BI4Pl0Ew.js} +2 -2
  51. package/dist/{internal-worker-guard-D7sfByyo.js.map → internal-worker-guard-BI4Pl0Ew.js.map} +1 -1
  52. package/dist/{internal-workspace-header-CU5OjLCB.js → internal-workspace-header-kg9FoNXK.js} +2 -2
  53. package/dist/{internal-workspace-header-CU5OjLCB.js.map → internal-workspace-header-kg9FoNXK.js.map} +1 -1
  54. package/dist/lifecycle-BpqAyaAj.js +2 -0
  55. package/dist/lifecycle-CL2WiyeQ.js +2 -0
  56. package/dist/{lifecycle-CtRA3Y7p.js → lifecycle-Cx1wVrLb.js} +3 -3
  57. package/dist/{lifecycle-CtRA3Y7p.js.map → lifecycle-Cx1wVrLb.js.map} +1 -1
  58. package/dist/{lifecycle-BCnarXJV.js → lifecycle-IwotwedG.js} +2 -2
  59. package/dist/{lifecycle-BCnarXJV.js.map → lifecycle-IwotwedG.js.map} +1 -1
  60. package/dist/main.js +20 -20
  61. package/dist/{mcp-workspace-header-B60WSR1Y.js → mcp-workspace-header-BYgWioLH.js} +2 -2
  62. package/dist/{mcp-workspace-header-B60WSR1Y.js.map → mcp-workspace-header-BYgWioLH.js.map} +1 -1
  63. package/dist/{models-COuMOfM6.js → models-DgLeb-Dv.js} +3 -3
  64. package/dist/{models-COuMOfM6.js.map → models-DgLeb-Dv.js.map} +1 -1
  65. package/dist/{orchestration-DjLA-lxB.js → orchestration-Dc3mO0G3.js} +3 -3
  66. package/dist/{orchestration-DjLA-lxB.js.map → orchestration-Dc3mO0G3.js.map} +1 -1
  67. package/dist/paths-BXXCVFvA.js +2 -0
  68. package/dist/{paths-De1Qh9Q9.js → paths-CMxlQEfQ.js} +3 -3
  69. package/dist/{paths-De1Qh9Q9.js.map → paths-CMxlQEfQ.js.map} +1 -1
  70. package/dist/{peer-mcp-personas-0_XU6nm9.js → peer-mcp-personas-CYWJSxPK.js} +11 -11
  71. package/dist/{peer-mcp-personas-0_XU6nm9.js.map → peer-mcp-personas-CYWJSxPK.js.map} +1 -1
  72. package/dist/{plan-review-hook-vL6BYmA8.js → plan-review-hook-BIMwWkh6.js} +4 -4
  73. package/dist/{plan-review-hook-vL6BYmA8.js.map → plan-review-hook-BIMwWkh6.js.map} +1 -1
  74. package/dist/{prompt-submit-hook-DfqufWC1.js → prompt-submit-hook-BcFpjHXg.js} +3 -3
  75. package/dist/{prompt-submit-hook-DfqufWC1.js.map → prompt-submit-hook-BcFpjHXg.js.map} +1 -1
  76. package/dist/{provision-DuCnZJj_.js → provision-AwSjt-vR.js} +5 -5
  77. package/dist/{provision-DuCnZJj_.js.map → provision-AwSjt-vR.js.map} +1 -1
  78. package/dist/{self-invocation-B84JCHps.js → self-invocation-cRl4RqfW.js} +2 -2
  79. package/dist/{self-invocation-B84JCHps.js.map → self-invocation-cRl4RqfW.js.map} +1 -1
  80. package/dist/{serve-DXFVR6A0.js → serve-BG2O6Slz.js} +13 -13
  81. package/dist/{serve-DXFVR6A0.js.map → serve-BG2O6Slz.js.map} +1 -1
  82. package/dist/{server-setup-CP8nGop3.js → server-setup-D7ABbEs1.js} +8 -8
  83. package/dist/{server-setup-CP8nGop3.js.map → server-setup-D7ABbEs1.js.map} +1 -1
  84. package/dist/{start-BHEjESCB.js → start-Dpwpnq99.js} +3 -3
  85. package/dist/{start-BHEjESCB.js.map → start-Dpwpnq99.js.map} +1 -1
  86. package/dist/{stop-gate-hook-BEU8qQrC.js → stop-gate-hook-BUCxa5Kc.js} +4 -4
  87. package/dist/{stop-gate-hook-BEU8qQrC.js.map → stop-gate-hook-BUCxa5Kc.js.map} +1 -1
  88. package/dist/{stop-gate-policy-qzqXfRxE.js → stop-gate-policy--bpyxxXU.js} +3 -3
  89. package/dist/{stop-gate-policy-qzqXfRxE.js.map → stop-gate-policy--bpyxxXU.js.map} +1 -1
  90. package/dist/{token-CCg-Ov5q.js → token-ClzS23Y2.js} +2 -2
  91. package/dist/{token-CCg-Ov5q.js.map → token-ClzS23Y2.js.map} +1 -1
  92. package/dist/{worker-dispatch-CH-NN5FW.js → worker-dispatch-DMIdrlsF.js} +2 -2
  93. package/dist/{worker-dispatch-CH-NN5FW.js.map → worker-dispatch-DMIdrlsF.js.map} +1 -1
  94. package/package.json +1 -1
  95. package/dist/aic-ledger-Cx_Ubon3.js.map +0 -1
  96. package/dist/claude-D0vuAqTV.js.map +0 -1
  97. package/dist/engine-DsVZuCrD.js +0 -2
  98. package/dist/internal-aic-status-C_swX3_7.js +0 -95
  99. package/dist/internal-aic-status-C_swX3_7.js.map +0 -1
  100. package/dist/lifecycle-B0USSJvd.js +0 -2
  101. package/dist/lifecycle-CQFjaQc0.js +0 -2
  102. package/dist/paths-DyX6SEnd.js +0 -2
package/dist/hooks.mjs CHANGED
@@ -34642,6 +34642,35 @@ var NANO_AIU_PER_CREDIT;
34642
34642
  var init_aic_usage = __esmMin((() => {
34643
34643
  NANO_AIU_PER_CREDIT = 1e9;
34644
34644
  }));
34645
+ //#endregion
34646
+ //#region src/lib/aic-ledger.ts
34647
+ /**
34648
+ * Per-launch AIC (AI Credits) ledger.
34649
+ *
34650
+ * Every route handler records upstream `copilot_usage` here as a side effect
34651
+ * (bytes pass through untouched). The totals feed two consumers:
34652
+ *
34653
+ * 1. `internal-aic-status` — runs as a SEPARATE process on every status-line
34654
+ * tick, so it cannot read in-process memory. Each `record()` therefore
34655
+ * also persists an atomic snapshot file; the ledger path is handed to the
34656
+ * spawned child via `GH_ROUTER_AIC_LEDGER` and baked into the statusLine
34657
+ * command.
34658
+ * 2. The `claude` exit summary — reads the in-process snapshot.
34659
+ *
34660
+ * Keyed per proxy process (one file per PID under `<APP_DIR>/aic-ledger/`),
34661
+ * so concurrent launches never mix. Snapshot writes are best-effort and never
34662
+ * throw into the request path. Stale files (dead PID, >24h) are swept at
34663
+ * startup of the next record and on shutdown.
34664
+ */
34665
+ function emptyModelTokens() {
34666
+ return {
34667
+ input: 0,
34668
+ cache_read: 0,
34669
+ cache_write: 0,
34670
+ output: 0,
34671
+ other: 0
34672
+ };
34673
+ }
34645
34674
  /**
34646
34675
  * Read a persisted snapshot file (the status-hook path). Returns undefined
34647
34676
  * when missing/unreadable — the hook then prints nothing.
@@ -34657,12 +34686,41 @@ function readAicSnapshotFile(file) {
34657
34686
  totalNanoAiu: Math.max(0, Math.floor(record.totalNanoAiu)),
34658
34687
  requests: Math.max(0, Math.floor(record.requests)),
34659
34688
  perModel: record.perModel && typeof record.perModel === "object" ? record.perModel : {},
34660
- perTokenType: record.perTokenType && typeof record.perTokenType === "object" ? record.perTokenType : {}
34689
+ perTokenType: record.perTokenType && typeof record.perTokenType === "object" ? record.perTokenType : {},
34690
+ tokensByModel: sanitizeModelTokens(record.tokensByModel)
34661
34691
  };
34662
34692
  } catch {
34663
34693
  return;
34664
34694
  }
34665
34695
  }
34696
+ /** Best-effort sanitize of persisted per-model token counts; `{}` when absent. */
34697
+ function sanitizeModelTokens(value) {
34698
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
34699
+ const out = {};
34700
+ for (const [model, counts] of Object.entries(value)) {
34701
+ if (!counts || typeof counts !== "object" || Array.isArray(counts)) continue;
34702
+ const entry = counts;
34703
+ const clean = emptyModelTokens();
34704
+ let ok = true;
34705
+ for (const field of [
34706
+ "input",
34707
+ "cache_read",
34708
+ "cache_write",
34709
+ "output",
34710
+ "other"
34711
+ ]) {
34712
+ const n = entry[field];
34713
+ if (n === void 0) continue;
34714
+ if (typeof n !== "number" || !Number.isFinite(n) || n < 0) {
34715
+ ok = false;
34716
+ break;
34717
+ }
34718
+ clean[field] = Math.floor(n);
34719
+ }
34720
+ if (ok) out[model] = clean;
34721
+ }
34722
+ return out;
34723
+ }
34666
34724
  /** Total credits for a snapshot. */
34667
34725
  function aicTotalCredits(snapshot) {
34668
34726
  return nanoAiuToCredits(snapshot.totalNanoAiu);
@@ -34968,7 +35026,7 @@ var internal_stop_hook_exports = /* @__PURE__ */ __exportAll({
34968
35026
  * no such handle. Hooks always receive piped/redirected stdin, so this never
34969
35027
  * blocks (guarded against an interactive TTY, and any error -> "").
34970
35028
  */
34971
- function readStdin$5() {
35029
+ function readStdin$6() {
34972
35030
  try {
34973
35031
  if (process.stdin.isTTY) return "";
34974
35032
  return readFileSync(0, "utf8");
@@ -35155,7 +35213,7 @@ var init_internal_stop_hook = __esmMin((() => {
35155
35213
  description: "Internal: the structural-gate Stop hook. Reads the Claude Code hook payload on stdin, runs the sealed gate, exits 2 (blocks the stop) on a red gate or gate-weakening diff."
35156
35214
  },
35157
35215
  async run() {
35158
- const stdin = readStdin$5();
35216
+ const stdin = readStdin$6();
35159
35217
  const reviewEnabled = stopReviewEnabled() && hookMcpRuntimeFromEnv() !== void 0;
35160
35218
  let transcriptPath = "";
35161
35219
  let userPrompt = "";
@@ -35323,7 +35381,7 @@ var internal_prompt_submit_exports = /* @__PURE__ */ __exportAll({ internalPromp
35323
35381
  * no such handle. Hooks always receive piped/redirected stdin, so this never
35324
35382
  * blocks (guarded against an interactive TTY, and any error -> "").
35325
35383
  */
35326
- function readStdin$4() {
35384
+ function readStdin$5() {
35327
35385
  try {
35328
35386
  if (process.stdin.isTTY) return "";
35329
35387
  return readFileSync(0, "utf8");
@@ -35360,7 +35418,7 @@ var init_internal_prompt_submit = __esmMin((() => {
35360
35418
  },
35361
35419
  async run() {
35362
35420
  try {
35363
- const stdin = readStdin$4();
35421
+ const stdin = readStdin$5();
35364
35422
  const steerEnabled = parseBoolEnv(process.env.GH_ROUTER_DISABLE_PROMPT_STEER) !== true;
35365
35423
  const runtime = hookMcpRuntimeFromEnv();
35366
35424
  let decision;
@@ -35655,7 +35713,7 @@ var init_plan_review_hook = __esmMin((() => {
35655
35713
  //#endregion
35656
35714
  //#region src/internal-plan-review.ts
35657
35715
  var internal_plan_review_exports = /* @__PURE__ */ __exportAll({ internalPlanReview: () => internalPlanReview });
35658
- function readStdin$3() {
35716
+ function readStdin$4() {
35659
35717
  try {
35660
35718
  if (process.stdin.isTTY) return "";
35661
35719
  return readFileSync(0, "utf8");
@@ -35748,7 +35806,7 @@ var init_internal_plan_review = __esmMin((() => {
35748
35806
  return;
35749
35807
  }
35750
35808
  const decision = await decidePlanReviewHook({
35751
- stdin: readStdin$3(),
35809
+ stdin: readStdin$4(),
35752
35810
  runtimeAvailable: true,
35753
35811
  debounce: filePlanReviewDebounce(stopReviewStateDir()),
35754
35812
  fallbackCwd: process.cwd()
@@ -35771,7 +35829,7 @@ var internal_session_bind_exports = /* @__PURE__ */ __exportAll({
35771
35829
  * teardown and trips a `uv_async_send` assertion; a synchronous read has no such
35772
35830
  * handle. Hooks always receive piped stdin (guarded against a TTY; any error -> "").
35773
35831
  */
35774
- function readStdin$2() {
35832
+ function readStdin$3() {
35775
35833
  try {
35776
35834
  if (process.stdin.isTTY) return "";
35777
35835
  return readFileSync(0, "utf8");
@@ -35865,7 +35923,7 @@ var init_internal_session_bind = __esmMin((() => {
35865
35923
  try {
35866
35924
  const out = typeof args.out === "string" ? args.out.trim() : "";
35867
35925
  if (!out) return;
35868
- const record = decodeSessionBind(readStdin$2());
35926
+ const record = decodeSessionBind(readStdin$3());
35869
35927
  if (record) writeSidecar(out, record);
35870
35928
  } catch {}
35871
35929
  process.exitCode = 0;
@@ -37678,7 +37736,7 @@ var internal_artifact_open_exports = /* @__PURE__ */ __exportAll({
37678
37736
  internalArtifactOpen: () => internalArtifactOpen,
37679
37737
  parseExitPlanPayload: () => parseExitPlanPayload
37680
37738
  });
37681
- function readStdin$1() {
37739
+ function readStdin$2() {
37682
37740
  try {
37683
37741
  if (process.stdin.isTTY) return "";
37684
37742
  return readFileSync(0, "utf8");
@@ -37778,7 +37836,7 @@ var init_internal_artifact_open = __esmMin((() => {
37778
37836
  },
37779
37837
  async run() {
37780
37838
  try {
37781
- const raw = readStdin$1();
37839
+ const raw = readStdin$2();
37782
37840
  let payload;
37783
37841
  try {
37784
37842
  payload = JSON.parse(raw);
@@ -37997,7 +38055,7 @@ var internal_worker_guard_exports = /* @__PURE__ */ __exportAll({ internalWorker
37997
38055
  * libuv FS request that races Windows process teardown. Hooks always receive
37998
38056
  * piped stdin (guarded against an interactive TTY; any error → "").
37999
38057
  */
38000
- function readStdin() {
38058
+ function readStdin$1() {
38001
38059
  try {
38002
38060
  if (process.stdin.isTTY) return "";
38003
38061
  return readFileSync(0, "utf8");
@@ -38028,7 +38086,7 @@ var init_internal_worker_guard = __esmMin((() => {
38028
38086
  },
38029
38087
  run(ctx) {
38030
38088
  try {
38031
- const stdin = readStdin();
38089
+ const stdin = readStdin$1();
38032
38090
  const workersKey = String(ctx.args["workers-key"] ?? "").trim();
38033
38091
  if (workersKey.length > 0) {
38034
38092
  const { output } = decideWorkerGuard({
@@ -38485,6 +38543,388 @@ var init_internal_max_dispatch_guard = __esmMin((() => {
38485
38543
  });
38486
38544
  }));
38487
38545
  //#endregion
38546
+ //#region src/lib/copilot-discount.ts
38547
+ function row(factor, source) {
38548
+ return {
38549
+ factor,
38550
+ source
38551
+ };
38552
+ }
38553
+ /**
38554
+ * Clamp a factor to usable range. Non-finite or negative factors (corrupt
38555
+ * table edit, bad injection) degrade to 1.0 — unfactored but never NaN,
38556
+ * never negative on screen.
38557
+ */
38558
+ function sanitizeFactor(raw) {
38559
+ return typeof raw === "number" && Number.isFinite(raw) && raw >= 0 ? raw : 1;
38560
+ }
38561
+ /**
38562
+ * Session discounted USD from a ledger snapshot. Total function: never
38563
+ * throws (guards non-finite/negative ledger values defensively — a corrupt
38564
+ * snapshot must degrade the status line, not crash the hook).
38565
+ */
38566
+ function discountedUsdForSnapshot(snapshot) {
38567
+ return discountedUsdWithFactors(snapshot, DISCOUNT_FACTORS);
38568
+ }
38569
+ /**
38570
+ * Factor-injectable core (exported for unit tests — e.g. proving a
38571
+ * fat-finger factor can never overstate spend). `factors` falls back to
38572
+ * 1.0 per model exactly like the table lookup.
38573
+ */
38574
+ function discountedUsdWithFactors(snapshot, factors) {
38575
+ const byModel = {};
38576
+ let sum = 0;
38577
+ for (const [model, entry] of Object.entries(snapshot.perModel)) {
38578
+ const nano = typeof entry.nanoAiu === "number" && Number.isFinite(entry.nanoAiu) ? Math.max(0, entry.nanoAiu) : 0;
38579
+ const factor = sanitizeFactor(factors[model]?.factor);
38580
+ const usd = nano / 1e9 * USD_PER_AIC_CREDIT * factor;
38581
+ byModel[model] = usd;
38582
+ sum += usd;
38583
+ }
38584
+ const cap = aicTotalCredits(snapshot) * USD_PER_AIC_CREDIT;
38585
+ if (!(sum > cap * (1 + 1e-9)) || sum <= 0) return {
38586
+ total: sum,
38587
+ byModel,
38588
+ capped: false
38589
+ };
38590
+ const scale = cap / sum;
38591
+ const scaled = {};
38592
+ for (const [model, usd] of Object.entries(byModel)) scaled[model] = usd * scale;
38593
+ return {
38594
+ total: cap,
38595
+ byModel: scaled,
38596
+ capped: true
38597
+ };
38598
+ }
38599
+ var USD_PER_AIC_CREDIT, DISCOUNT_FACTORS;
38600
+ var init_copilot_discount = __esmMin((() => {
38601
+ init_aic_ledger();
38602
+ USD_PER_AIC_CREDIT = .01;
38603
+ DISCOUNT_FACTORS = Object.freeze({
38604
+ "gpt-5.6-sol": row(.28, "0.27-0.29 across 28d/MTD/7d/pre/9-9; write-mix refuted"),
38605
+ "claude-opus-5": row(.62, "0.61-0.64 across windows; 9/7 1.02 excluded as single-day artifact"),
38606
+ "claude-sonnet-5": row(.66, "0.62-0.71 across windows"),
38607
+ "gpt-5.3-codex": row(.6, "0.59-0.62 across windows"),
38608
+ "gpt-5.4": row(.6, "single snapshot 0.598"),
38609
+ "gemini-3.7-flash": row(.29, "September regime; pre-Sep 1.00x — volatile"),
38610
+ "gpt-5.6-luna": row(1, "long-tier portal + writes, no discount"),
38611
+ "grok-4.6": row(1, "portal default, six windows"),
38612
+ "gemini-3.8-flash": row(1, "portal exact, six windows to the dollar")
38613
+ });
38614
+ }));
38615
+ //#endregion
38616
+ //#region src/lib/default-statusline.ts
38617
+ /**
38618
+ * Rich default status line for `github-router claude` sessions.
38619
+ *
38620
+ * Replaces the need for users to maintain their own PowerShell/bash status
38621
+ * script: `internal-aic-status` renders these segments natively from the
38622
+ * Claude Code stdin JSON, then pins the `[AIC x.xx]` ledger fragment ahead
38623
+ * of them. Zero dependencies, cross-platform (Windows 11 is the primary
38624
+ * target — no `powershell`/`bash`/`jq` required), and fast (no shell
38625
+ * startup; one bounded `git rev-parse` at most).
38626
+ *
38627
+ * Segment order (left = highest priority; narrow terminals drop from the
38628
+ * right, `AIC` is never dropped):
38629
+ *
38630
+ * `[AIC] | [ctx bar] % | model | dir (branch) | ~$actual | in/out | dur | +a -r`
38631
+ *
38632
+ * The `$` segment is the DISCOUNTED actual (`~$` = factor-discounted
38633
+ * session total from `copilot-discount.ts`, capped at AIC × $0.01) — NOT
38634
+ * Claude's `cost.total_cost_usd`, which prices at Anthropic list rates and
38635
+ * reads ~10-100x high against Copilot billing (a 28-day sample: 41.1B tokens
38636
+ * for $5,142 ≈ $0.13/1M blended vs $15-75/1M list). Rendering list price
38637
+ * next to the real `[AIC]` billing unit would show two contradictory
38638
+ * numbers, so list price is deliberately not rendered at all.
38639
+ *
38640
+ * Width comes from `COLUMNS` (set by Claude Code for status scripts since
38641
+ * v2.1.153); fallback is 120 when unset/unparseable so a missing `COLUMNS`
38642
+ * never needlessly drops segments.
38643
+ */
38644
+ function asRecord(value) {
38645
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
38646
+ return value;
38647
+ }
38648
+ function finiteNumber(value) {
38649
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
38650
+ }
38651
+ function nonEmptyString(value) {
38652
+ return typeof value === "string" && value.length > 0 ? value : void 0;
38653
+ }
38654
+ /**
38655
+ * Parse Claude Code's status-line stdin JSON into the fields we render.
38656
+ * Total function: malformed JSON or unexpected shapes yield `{}` (every
38657
+ * segment then renders its `--` placeholder or is omitted) — never throws.
38658
+ */
38659
+ function parseStatusInput(raw) {
38660
+ if (!raw.trim()) return {};
38661
+ let parsed;
38662
+ try {
38663
+ parsed = JSON.parse(raw);
38664
+ } catch {
38665
+ return {};
38666
+ }
38667
+ const root = asRecord(parsed);
38668
+ if (!root) return {};
38669
+ const out = {};
38670
+ const ctx = asRecord(root.context_window);
38671
+ if (ctx) {
38672
+ const pct = finiteNumber(ctx.used_percentage);
38673
+ if (pct !== void 0) out.usedPct = pct;
38674
+ const tIn = finiteNumber(ctx.total_input_tokens);
38675
+ if (tIn !== void 0 && tIn >= 0) out.totalInputTokens = Math.floor(tIn);
38676
+ const tOut = finiteNumber(ctx.total_output_tokens);
38677
+ if (tOut !== void 0 && tOut >= 0) out.totalOutputTokens = Math.floor(tOut);
38678
+ }
38679
+ const cost = asRecord(root.cost);
38680
+ if (cost) {
38681
+ const dur = finiteNumber(cost.total_duration_ms);
38682
+ if (dur !== void 0 && dur >= 0) out.totalDurationMs = dur;
38683
+ const la = finiteNumber(cost.total_lines_added);
38684
+ if (la !== void 0 && la >= 0) out.linesAdded = Math.floor(la);
38685
+ const lr = finiteNumber(cost.total_lines_removed);
38686
+ if (lr !== void 0 && lr >= 0) out.linesRemoved = Math.floor(lr);
38687
+ }
38688
+ const workspace = asRecord(root.workspace);
38689
+ const cwd = (workspace ? nonEmptyString(workspace.current_dir) : void 0) ?? nonEmptyString(root.cwd);
38690
+ if (cwd) out.cwd = cwd;
38691
+ const model = asRecord(root.model);
38692
+ if (model) {
38693
+ const name = nonEmptyString(model.display_name) ?? nonEmptyString(model.id);
38694
+ if (name) out.modelName = name;
38695
+ }
38696
+ return out;
38697
+ }
38698
+ /** Compact token count: `999` → `999`, `15234` → `15.2k`, `2.1M`. */
38699
+ function compactTokens(n) {
38700
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
38701
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
38702
+ return `${n}`;
38703
+ }
38704
+ /** `45000` → `0m45s`, `198000` → `3m18s`, `3720000` → `1h02m`. */
38705
+ function formatDurationMs(ms) {
38706
+ const totalSeconds = Math.floor(ms / 1e3);
38707
+ const hours = Math.floor(totalSeconds / 3600);
38708
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
38709
+ const seconds = totalSeconds % 60;
38710
+ if (hours >= 1) return `${hours}h${String(minutes).padStart(2, "0")}m`;
38711
+ return `${minutes}m${String(seconds).padStart(2, "0")}s`;
38712
+ }
38713
+ /** Basename of a cwd, tolerant of `/` and `\` separators + trailing slash. */
38714
+ function dirLeaf(cwd) {
38715
+ const trimmed = cwd.replace(/[/\\]+$/, "");
38716
+ const parts = trimmed.split(/[/\\]/);
38717
+ return parts[parts.length - 1] ?? trimmed;
38718
+ }
38719
+ function buildCtxSegment(usedPct) {
38720
+ if (usedPct === void 0 || !Number.isFinite(usedPct)) return {
38721
+ text: `${DIM}[----------] --%${RESET}`,
38722
+ plain: "[----------] --%"
38723
+ };
38724
+ const u = Math.max(0, Math.min(100, Math.round(usedPct)));
38725
+ let filled = Math.round(u / 100 * CTX_BAR_LEN);
38726
+ if (filled > CTX_BAR_LEN) filled = CTX_BAR_LEN;
38727
+ if (filled < 0) filled = 0;
38728
+ const bar = `#`.repeat(filled) + `-`.repeat(CTX_BAR_LEN - filled);
38729
+ return {
38730
+ text: `${u < 50 ? GREEN : u < 75 ? YELLOW : RED}[${bar}] ${u}%${RESET}`,
38731
+ plain: `[${bar}] ${u}%`
38732
+ };
38733
+ }
38734
+ /**
38735
+ * Discounted-actual `$` segment. Callers pass the capped total from
38736
+ * `discountedUsdForSnapshot` — this function only formats. Placeholder
38737
+ * `~$--` until the first priced upstream response lands in the ledger.
38738
+ * Thresholds are tuned for ACTUALS scale (a session runs ~$0.10-2.00) —
38739
+ * not the old list-price scale.
38740
+ */
38741
+ function buildUsdSegment(actualUsd) {
38742
+ if (actualUsd === void 0 || !Number.isFinite(actualUsd) || actualUsd < 0) return {
38743
+ text: `${DIM}~$--${RESET}`,
38744
+ plain: "~$--"
38745
+ };
38746
+ const c = actualUsd;
38747
+ const color = c < 1 ? GREEN : c < 5 ? YELLOW : c < 20 ? ORANGE : RED;
38748
+ const formatted = c < 10 ? c.toFixed(2) : c.toFixed(1);
38749
+ return {
38750
+ text: `${color}~$${formatted}${RESET}`,
38751
+ plain: `~$${formatted}`
38752
+ };
38753
+ }
38754
+ function buildToksSegment(tIn, tOut) {
38755
+ if (tIn === void 0 || tOut === void 0) return {
38756
+ text: `${DIM}--/--${RESET}`,
38757
+ plain: "--/--"
38758
+ };
38759
+ const cin = compactTokens(tIn);
38760
+ const cout = compactTokens(tOut);
38761
+ return {
38762
+ text: `${CYAN}${cin}${RESET}${DIM}/${RESET}${MAGENTA}${cout}${RESET}`,
38763
+ plain: `${cin}/${cout}`
38764
+ };
38765
+ }
38766
+ function buildDurSegment(durMs) {
38767
+ if (durMs === void 0 || !Number.isFinite(durMs) || durMs < 0) return {
38768
+ text: `${DIM}--${RESET}`,
38769
+ plain: "--"
38770
+ };
38771
+ const formatted = formatDurationMs(durMs);
38772
+ return {
38773
+ text: `${DIM}${formatted}${RESET}`,
38774
+ plain: formatted
38775
+ };
38776
+ }
38777
+ function buildLinesSegment(added, removed) {
38778
+ const a = added ?? 0;
38779
+ const r = removed ?? 0;
38780
+ return {
38781
+ text: `${GREEN}+${a}${RESET} ${RED}-${r}${RESET}`,
38782
+ plain: `+${a} -${r}`
38783
+ };
38784
+ }
38785
+ function buildModelSegment(modelName) {
38786
+ if (!modelName || modelName === "--") return void 0;
38787
+ return {
38788
+ text: `${MAGENTA}${modelName}${RESET}`,
38789
+ plain: modelName
38790
+ };
38791
+ }
38792
+ function buildDirGitSegment(cwd, branch) {
38793
+ const leaf = cwd ? dirLeaf(cwd) : "";
38794
+ const br = branch && branch !== "HEAD" ? branch : "";
38795
+ if (!leaf && !br) return void 0;
38796
+ if (leaf && br) return {
38797
+ text: `${BLUE}${leaf}${RESET} ${CYAN}(${br})${RESET}`,
38798
+ plain: `${leaf} (${br})`
38799
+ };
38800
+ if (leaf) return {
38801
+ text: `${BLUE}${leaf}${RESET}`,
38802
+ plain: leaf
38803
+ };
38804
+ return {
38805
+ text: `${CYAN}${br}${RESET}`,
38806
+ plain: br
38807
+ };
38808
+ }
38809
+ /**
38810
+ * Resolve the current git branch for `cwd`. Bounded (`GIT_TIMEOUT_MS`) and
38811
+ * total: any failure (no git binary, not a repo, detached `HEAD`, timeout,
38812
+ * `%` in a Windows path that cmd.exe cannot safely quote) yields `""`
38813
+ * (caller omits the branch) — never throws.
38814
+ */
38815
+ function resolveGitBranch(cwd) {
38816
+ try {
38817
+ if (!cwd) return "";
38818
+ const gitPath = resolveExecutable("git");
38819
+ if (!gitPath) return "";
38820
+ const { command, args, shell } = buildExecInvocation([
38821
+ gitPath,
38822
+ "-C",
38823
+ cwd,
38824
+ "rev-parse",
38825
+ "--abbrev-ref",
38826
+ "HEAD"
38827
+ ]);
38828
+ const result = spawnSync(command, args, {
38829
+ shell,
38830
+ timeout: GIT_TIMEOUT_MS,
38831
+ encoding: "utf8",
38832
+ stdio: [
38833
+ "ignore",
38834
+ "pipe",
38835
+ "ignore"
38836
+ ]
38837
+ });
38838
+ if (result.error) return "";
38839
+ const out = typeof result.stdout === "string" ? result.stdout.trim() : "";
38840
+ if (!out || out === "HEAD") return "";
38841
+ return out.split("\n")[0]?.trim() ?? "";
38842
+ } catch {
38843
+ return "";
38844
+ }
38845
+ }
38846
+ /** Terminal width for the drop-right-to-left assembly. */
38847
+ function terminalWidth(env = process.env) {
38848
+ const raw = env.COLUMNS;
38849
+ if (raw !== void 0) {
38850
+ const n = Number(raw);
38851
+ if (Number.isInteger(n) && n > 0) return n;
38852
+ }
38853
+ return WIDTH_FALLBACK;
38854
+ }
38855
+ /**
38856
+ * Assemble the full status line. `aicFragment` (e.g. `[AIC 12.42]`) is
38857
+ * PINNED — it renders even when nothing else fits. `actualUsd` feeds the
38858
+ * `~$` segment (the capped factor-discounted total from
38859
+ * `discountedUsdForSnapshot`, same snapshot the fragment came from).
38860
+ * Droppable segments in priority order: ctx, model, dir/git, ~$, toks,
38861
+ * dur, lines.
38862
+ */
38863
+ function assembleStatusLine(aicFragment, input, opts = {}) {
38864
+ const aic = aicFragment.trim();
38865
+ const aicText = aic ? `${AIC_COLOR}${aic}${RESET}` : "";
38866
+ const droppable = [buildCtxSegment(input.usedPct)];
38867
+ const model = buildModelSegment(input.modelName);
38868
+ if (model) droppable.push(model);
38869
+ const dirGit = buildDirGitSegment(input.cwd, opts.branch);
38870
+ if (dirGit) droppable.push(dirGit);
38871
+ droppable.push(buildUsdSegment(opts.actualUsd), buildToksSegment(input.totalInputTokens, input.totalOutputTokens), buildDurSegment(input.totalDurationMs), buildLinesSegment(input.linesAdded, input.linesRemoved));
38872
+ const width = opts.width ?? terminalWidth();
38873
+ const sepLen = 3;
38874
+ let count = droppable.length;
38875
+ while (count > 0) {
38876
+ let total = aic ? aic.length : 0;
38877
+ for (let i = 0; i < count; i++) {
38878
+ if (aic || i > 0) total += sepLen;
38879
+ total += droppable[i].plain.length;
38880
+ }
38881
+ if (total <= width) break;
38882
+ if (count === 1 && !aic) break;
38883
+ count--;
38884
+ }
38885
+ const parts = [];
38886
+ if (aic) parts.push(aicText);
38887
+ for (let i = 0; i < count; i++) parts.push(droppable[i].text);
38888
+ if (parts.length === 0) return "";
38889
+ return parts.join(SEP_TEXT);
38890
+ }
38891
+ /**
38892
+ * Convenience: parse stdin JSON + assemble with branch resolution.
38893
+ * `branchOverride` skips the `git` spawn (used by tests). `actualUsd`
38894
+ * comes from `discountedUsdForSnapshot` on the same ledger snapshot as
38895
+ * `aicFragment` (read by the caller — this module never touches the
38896
+ * ledger file).
38897
+ */
38898
+ function buildRichStatusLine(stdinRaw, aicFragment, opts = {}) {
38899
+ const input = parseStatusInput(stdinRaw);
38900
+ const branch = opts.branchOverride !== void 0 ? opts.branchOverride : resolveGitBranch(input.cwd);
38901
+ return assembleStatusLine(aicFragment, input, {
38902
+ ...opts.width !== void 0 ? { width: opts.width } : {},
38903
+ ...branch ? { branch } : {},
38904
+ ...opts.actualUsd !== void 0 ? { actualUsd: opts.actualUsd } : {}
38905
+ });
38906
+ }
38907
+ var ESC, RESET, DIM, GREEN, YELLOW, RED, ORANGE, CYAN, MAGENTA, BLUE, AIC_COLOR, CTX_BAR_LEN, GIT_TIMEOUT_MS, WIDTH_FALLBACK, SEP_TEXT;
38908
+ var init_default_statusline = __esmMin((() => {
38909
+ init_copilot_discount();
38910
+ init_exec();
38911
+ ESC = "\x1B";
38912
+ RESET = `${ESC}[0m`;
38913
+ DIM = `${ESC}[90m`;
38914
+ GREEN = `${ESC}[32m`;
38915
+ YELLOW = `${ESC}[33m`;
38916
+ RED = `${ESC}[31m`;
38917
+ ORANGE = `${ESC}[38;5;208m`;
38918
+ CYAN = `${ESC}[36m`;
38919
+ MAGENTA = `${ESC}[35m`;
38920
+ BLUE = `${ESC}[34m`;
38921
+ AIC_COLOR = `${ESC}[1;33m`;
38922
+ CTX_BAR_LEN = 10;
38923
+ GIT_TIMEOUT_MS = 300;
38924
+ WIDTH_FALLBACK = 120;
38925
+ SEP_TEXT = `${DIM} | ${RESET}`;
38926
+ }));
38927
+ //#endregion
38488
38928
  //#region src/internal-aic-status.ts
38489
38929
  var internal_aic_status_exports = /* @__PURE__ */ __exportAll({
38490
38930
  composeAicStatusLine: () => composeAicStatusLine,
@@ -38492,14 +38932,18 @@ var internal_aic_status_exports = /* @__PURE__ */ __exportAll({
38492
38932
  runUserStatusLine: () => runUserStatusLine
38493
38933
  });
38494
38934
  /**
38495
- * Read stdin synchronously and discard. Claude Code pipes session JSON to the
38496
- * statusLine command; an unread pipe can SIGPIPE the writer on some platforms.
38935
+ * Read stdin synchronously. Claude Code pipes session JSON to the
38936
+ * statusLine command; an unread pipe can SIGPIPE the writer on some
38937
+ * platforms. Returns "" when stdin is a TTY or unreadable — callers render
38938
+ * placeholders/omit segments rather than failing.
38497
38939
  */
38498
- function drainStdin() {
38940
+ function readStdin() {
38499
38941
  try {
38500
- if (process.stdin.isTTY) return;
38501
- readFileSync(0, "utf8");
38502
- } catch {}
38942
+ if (process.stdin.isTTY) return "";
38943
+ return readFileSync(0, "utf8");
38944
+ } catch {
38945
+ return "";
38946
+ }
38503
38947
  }
38504
38948
  /**
38505
38949
  * Pure core: compose the status line from an AIC fragment and a user line.
@@ -38536,31 +38980,48 @@ function runUserStatusLine(command, timeoutMs = 5e3) {
38536
38980
  return "";
38537
38981
  }
38538
38982
  }
38539
- function readAicFragment() {
38983
+ function readAicSnapshot() {
38540
38984
  try {
38541
38985
  const ledgerPath = process.env.GH_ROUTER_AIC_LEDGER;
38542
- if (!ledgerPath) return "";
38986
+ if (!ledgerPath) return { fragment: "" };
38543
38987
  const snapshot = readAicSnapshotFile(ledgerPath);
38544
- if (!snapshot) return "";
38545
- return formatAicStatus(snapshot);
38988
+ if (!snapshot) return { fragment: "" };
38989
+ if (snapshot.requests === 0 || snapshot.totalNanoAiu <= 0) return { fragment: "" };
38990
+ return {
38991
+ fragment: formatAicStatus(snapshot),
38992
+ actualUsd: discountedUsdForSnapshot(snapshot).total
38993
+ };
38546
38994
  } catch {
38547
- return "";
38995
+ return { fragment: "" };
38548
38996
  }
38549
38997
  }
38550
38998
  var internalAicStatus;
38551
38999
  var init_internal_aic_status = __esmMin((() => {
38552
39000
  init_dist$1();
38553
39001
  init_aic_ledger();
39002
+ init_copilot_discount();
39003
+ init_default_statusline();
38554
39004
  internalAicStatus = defineCommand({
38555
39005
  meta: {
38556
39006
  name: "internal-aic-status",
38557
- description: "Internal: Claude Code statusLine handler printing this session's AIC total."
39007
+ description: "Internal: Claude Code statusLine handler printing AIC total + rich session line."
38558
39008
  },
38559
39009
  async run() {
38560
- drainStdin();
38561
- const aic = readAicFragment();
39010
+ const stdinRaw = readStdin();
39011
+ const { fragment: aic, actualUsd } = readAicSnapshot();
38562
39012
  const userCommand = process.env.GH_ROUTER_AIC_USER_STATUSLINE ?? "";
38563
- const line = composeAicStatusLine(aic, userCommand ? runUserStatusLine(userCommand) : "");
39013
+ if (userCommand.trim()) {
39014
+ const line = composeAicStatusLine(aic, runUserStatusLine(userCommand));
39015
+ if (line) process.stdout.write(`${line}\n`);
39016
+ process.exitCode = 0;
39017
+ return;
39018
+ }
39019
+ let line;
39020
+ try {
39021
+ line = buildRichStatusLine(stdinRaw, aic, { ...actualUsd !== void 0 ? { actualUsd } : {} });
39022
+ } catch {
39023
+ line = aic.trim();
39024
+ }
38564
39025
  if (line) process.stdout.write(`${line}\n`);
38565
39026
  process.exitCode = 0;
38566
39027
  }
package/dist/hooks.sha256 CHANGED
@@ -1 +1 @@
1
- bb70a937fad432cbe889975cec5285740b7389b091684b0b9759a1ec137d9299
1
+ a3747ee44a5aac64ec5a952b9efb60112356d31d45f8d04ae939401d0d96a7e5