github-router 0.3.314 → 0.3.316

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