pi-harness-runtime 0.9.7 → 0.9.10

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 (134) hide show
  1. package/CHANGELOG.md +28 -12
  2. package/README.md +42 -5
  3. package/footer-status.ts +108 -11
  4. package/harness/e2e/minimax-quota-scraper.ts +46 -4
  5. package/index.ts +224 -16
  6. package/mirror.ts +194 -18
  7. package/package.json +1 -1
  8. package/packages/a2a-adapter/package.json +1 -1
  9. package/packages/auth/package.json +1 -1
  10. package/packages/autonomous-refactor/package.json +1 -1
  11. package/packages/cache-strategy/package.json +1 -1
  12. package/packages/capability-registry/package.json +1 -1
  13. package/packages/checkpoint/package.json +1 -1
  14. package/packages/cli-plugin-sdk/package.json +1 -1
  15. package/packages/code-generation/package.json +1 -1
  16. package/packages/code-review/package.json +1 -1
  17. package/packages/codex-adapter/package.json +1 -1
  18. package/packages/context-compiler/package.json +1 -1
  19. package/packages/context-manager/package.json +1 -1
  20. package/packages/cookie-sanitizer/README.md +141 -0
  21. package/packages/cookie-sanitizer/dist/atomic-write.d.ts +38 -0
  22. package/packages/cookie-sanitizer/dist/atomic-write.d.ts.map +1 -0
  23. package/packages/cookie-sanitizer/dist/atomic-write.js +67 -0
  24. package/packages/cookie-sanitizer/dist/atomic-write.js.map +1 -0
  25. package/packages/cookie-sanitizer/dist/detect-format.d.ts +36 -0
  26. package/packages/cookie-sanitizer/dist/detect-format.d.ts.map +1 -0
  27. package/packages/cookie-sanitizer/dist/detect-format.js +88 -0
  28. package/packages/cookie-sanitizer/dist/detect-format.js.map +1 -0
  29. package/packages/cookie-sanitizer/dist/index.d.ts +41 -0
  30. package/packages/cookie-sanitizer/dist/index.d.ts.map +1 -0
  31. package/packages/cookie-sanitizer/dist/index.js +39 -0
  32. package/packages/cookie-sanitizer/dist/index.js.map +1 -0
  33. package/packages/cookie-sanitizer/dist/infer-provider.d.ts +29 -0
  34. package/packages/cookie-sanitizer/dist/infer-provider.d.ts.map +1 -0
  35. package/packages/cookie-sanitizer/dist/infer-provider.js +96 -0
  36. package/packages/cookie-sanitizer/dist/infer-provider.js.map +1 -0
  37. package/packages/cookie-sanitizer/dist/normalize.d.ts +26 -0
  38. package/packages/cookie-sanitizer/dist/normalize.d.ts.map +1 -0
  39. package/packages/cookie-sanitizer/dist/normalize.js +75 -0
  40. package/packages/cookie-sanitizer/dist/normalize.js.map +1 -0
  41. package/packages/cookie-sanitizer/dist/parse-json.d.ts +45 -0
  42. package/packages/cookie-sanitizer/dist/parse-json.d.ts.map +1 -0
  43. package/packages/cookie-sanitizer/dist/parse-json.js +108 -0
  44. package/packages/cookie-sanitizer/dist/parse-json.js.map +1 -0
  45. package/packages/cookie-sanitizer/dist/parse-netscape.d.ts +43 -0
  46. package/packages/cookie-sanitizer/dist/parse-netscape.d.ts.map +1 -0
  47. package/packages/cookie-sanitizer/dist/parse-netscape.js +99 -0
  48. package/packages/cookie-sanitizer/dist/parse-netscape.js.map +1 -0
  49. package/packages/cookie-sanitizer/dist/sync.d.ts +62 -0
  50. package/packages/cookie-sanitizer/dist/sync.d.ts.map +1 -0
  51. package/packages/cookie-sanitizer/dist/sync.js +266 -0
  52. package/packages/cookie-sanitizer/dist/sync.js.map +1 -0
  53. package/packages/cookie-sanitizer/dist/types.d.ts +100 -0
  54. package/packages/cookie-sanitizer/dist/types.d.ts.map +1 -0
  55. package/packages/cookie-sanitizer/dist/types.js +13 -0
  56. package/packages/cookie-sanitizer/dist/types.js.map +1 -0
  57. package/packages/cookie-sanitizer/dist/watcher.d.ts +51 -0
  58. package/packages/cookie-sanitizer/dist/watcher.d.ts.map +1 -0
  59. package/packages/cookie-sanitizer/dist/watcher.js +148 -0
  60. package/packages/cookie-sanitizer/dist/watcher.js.map +1 -0
  61. package/packages/cookie-sanitizer/package.json +27 -0
  62. package/packages/cookie-sanitizer/src/atomic-write.ts +79 -0
  63. package/packages/cookie-sanitizer/src/detect-format.ts +102 -0
  64. package/packages/cookie-sanitizer/src/index.ts +89 -0
  65. package/packages/cookie-sanitizer/src/infer-provider.ts +103 -0
  66. package/packages/cookie-sanitizer/src/normalize.ts +92 -0
  67. package/packages/cookie-sanitizer/src/parse-json.ts +122 -0
  68. package/packages/cookie-sanitizer/src/parse-netscape.ts +118 -0
  69. package/packages/cookie-sanitizer/src/sync.ts +304 -0
  70. package/packages/cookie-sanitizer/src/types.ts +101 -0
  71. package/packages/cookie-sanitizer/src/watcher.ts +180 -0
  72. package/packages/cookie-sanitizer/test/infer.test.ts +110 -0
  73. package/packages/cookie-sanitizer/test/normalize.test.ts +113 -0
  74. package/packages/cookie-sanitizer/test/parse.test.ts +204 -0
  75. package/packages/cookie-sanitizer/test/sync.test.ts +231 -0
  76. package/packages/cookie-sanitizer/test/watcher.test.ts +27 -0
  77. package/packages/cookie-sanitizer/tsconfig.json +8 -0
  78. package/packages/cost-optimizer/package.json +1 -1
  79. package/packages/django-plugin/package.json +1 -1
  80. package/packages/doc-generator/package.json +1 -1
  81. package/packages/evaluation-engine/package.json +1 -1
  82. package/packages/evaluation-runner/package.json +1 -1
  83. package/packages/experience-replay/package.json +1 -1
  84. package/packages/feedback-collector/package.json +1 -1
  85. package/packages/framework-detector/package.json +1 -1
  86. package/packages/framework-plugin-sdk/package.json +1 -1
  87. package/packages/frappe-plugin/package.json +1 -1
  88. package/packages/generic-web-plugin/package.json +1 -1
  89. package/packages/health-monitor/package.json +1 -1
  90. package/packages/laravel-plugin/package.json +1 -1
  91. package/packages/learning-engine/package.json +1 -1
  92. package/packages/mcp-adapter/package.json +1 -1
  93. package/packages/memory-engine/package.json +1 -1
  94. package/packages/model-registry/package.json +1 -1
  95. package/packages/nextjs-plugin/package.json +1 -1
  96. package/packages/notification/package.json +1 -1
  97. package/packages/observability/package.json +1 -1
  98. package/packages/performance-optimizer/package.json +1 -1
  99. package/packages/project-analyzer/package.json +1 -1
  100. package/packages/prompt-compiler/package.json +1 -1
  101. package/packages/prompt-versioning/package.json +1 -1
  102. package/packages/provider-adapter-sdk/package.json +1 -1
  103. package/packages/provider-router/package.json +1 -1
  104. package/packages/provider-selector/package.json +1 -1
  105. package/packages/providers/dist/index.d.ts +1 -0
  106. package/packages/providers/dist/index.d.ts.map +1 -1
  107. package/packages/providers/dist/index.js +1 -0
  108. package/packages/providers/dist/index.js.map +1 -1
  109. package/packages/providers/dist/provider-id.d.ts +39 -0
  110. package/packages/providers/dist/provider-id.d.ts.map +1 -0
  111. package/packages/providers/dist/provider-id.js +96 -0
  112. package/packages/providers/dist/provider-id.js.map +1 -0
  113. package/packages/providers/package.json +1 -1
  114. package/packages/providers/src/index.ts +1 -0
  115. package/packages/providers/src/provider-id.ts +110 -0
  116. package/packages/quota-manager/package.json +1 -1
  117. package/packages/rate-limiter/package.json +1 -1
  118. package/packages/react-vite-plugin/package.json +1 -1
  119. package/packages/release-manager/package.json +1 -1
  120. package/packages/requirement-compiler/package.json +1 -1
  121. package/packages/runtime/package.json +1 -1
  122. package/packages/scheduler/package.json +1 -1
  123. package/packages/session/package.json +1 -1
  124. package/packages/shared-context/package.json +1 -1
  125. package/packages/skill-registry/package.json +1 -1
  126. package/packages/sprint-planner/package.json +1 -1
  127. package/packages/task-compiler/package.json +1 -1
  128. package/packages/test-data-generator/package.json +1 -1
  129. package/packages/test-generator/package.json +1 -1
  130. package/packages/token-estimation/package.json +1 -1
  131. package/packages/token-optimizer/package.json +1 -1
  132. package/packages/tui/package.json +1 -1
  133. package/packages/types/package.json +1 -1
  134. package/packages/worktree/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,8 +2,23 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
- ### [0.9.7](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.5...v0.9.7) (2026-07-16)
5
+ ### [0.9.10](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.8...v0.9.10) (2026-07-23)
6
+
7
+ ### Bug Fixes
6
8
 
9
+ * **tests:** update footer-status expectations for provider label prefix
10
+
11
+ ### [0.9.9](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.8...v0.9.9) (2026-07-23)
12
+
13
+ ### Bug Fixes
14
+
15
+ * **release:** test workflow trigger via manual version bump
16
+
17
+ ### [0.9.8](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.5...v0.9.8) (2026-07-23)
18
+
19
+ ### Features
20
+
21
+ * **cookie-sanitizer:** discoverable drop folder + per-provider mirror ([772eda5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/772eda5c5c698572a1cef2dc93df0278e9a380ee))
7
22
 
8
23
  ### Bug Fixes
9
24
 
@@ -12,6 +27,18 @@ All notable changes to this project will be documented in this file. See [standa
12
27
 
13
28
  ### [0.9.3](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.5.0-beta.1...v0.9.3) (2026-07-14)
14
29
 
30
+ ### Bug Fixes
31
+
32
+ * auto-resume pi dev with literal resume ([#51](https://github.com/ManotLuijiu/pi-harness-runtime/issues/51)) ([fd98762](https://github.com/ManotLuijiu/pi-harness-runtime/commit/fd987629f6062a4a7ff7a28d9dfb607962f5a4a9))
33
+
34
+ ### [0.9.7](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.5...v0.9.7) (2026-07-16)
35
+
36
+ ### Bug Fixes
37
+
38
+ * harden auto-resume after compaction ([13378e8](https://github.com/ManotLuijiu/pi-harness-runtime/commit/13378e852c984ead0e66f0adeffc073ff58af50c))
39
+ * resume after output-limit error stops ([8c75a24](https://github.com/ManotLuijiu/pi-harness-runtime/commit/8c75a2432082c4f9ba6296ffe2d04b636863bf2f))
40
+
41
+ ### [0.9.3](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.5.0-beta.1...v0.9.3) (2026-07-14)
15
42
 
16
43
  ### Bug Fixes
17
44
 
@@ -19,21 +46,18 @@ All notable changes to this project will be documented in this file. See [standa
19
46
 
20
47
  ### [0.9.6](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.5...v0.9.6) (2026-07-16)
21
48
 
22
-
23
49
  ### Bug Fixes
24
50
 
25
51
  * harden auto-resume after compaction ([13378e8](https://github.com/ManotLuijiu/pi-harness-runtime/commit/13378e852c984ead0e66f0adeffc073ff58af50c))
26
52
 
27
53
  ### [0.9.3](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.5.0-beta.1...v0.9.3) (2026-07-14)
28
54
 
29
-
30
55
  ### Bug Fixes
31
56
 
32
57
  * auto-resume pi dev with literal resume ([#51](https://github.com/ManotLuijiu/pi-harness-runtime/issues/51)) ([fd98762](https://github.com/ManotLuijiu/pi-harness-runtime/commit/fd987629f6062a4a7ff7a28d9dfb607962f5a4a9))
33
58
 
34
59
  ### [0.9.5](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.4...v0.9.5) (2026-07-15)
35
60
 
36
-
37
61
  ### Features
38
62
 
39
63
  * add 15 packages from subagent batch + test expansions ([c82d898](https://github.com/ManotLuijiu/pi-harness-runtime/commit/c82d898277779886cdd1f7f42087698762becde3))
@@ -45,14 +69,12 @@ All notable changes to this project will be documented in this file. See [standa
45
69
  * add package metadata for auth, scheduler, shared-context, token-estimation ([67e2621](https://github.com/ManotLuijiu/pi-harness-runtime/commit/67e26211633f01de4f0d5eca76abc4d40d1ee0ea))
46
70
  * flesh out RFC 71-75 specs + fix codex-adapter LSP types ([38b8643](https://github.com/ManotLuijiu/pi-harness-runtime/commit/38b86438ef45f75d1c34caf5b4f401d29c76d44d))
47
71
 
48
-
49
72
  ### Bug Fixes
50
73
 
51
74
  * auto-resume after compaction and output limits ([0cff2bc](https://github.com/ManotLuijiu/pi-harness-runtime/commit/0cff2bcf9035a699496e2726b74a1576863cf82e))
52
75
 
53
76
  ### [0.9.4](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.5.0-beta.1...v0.9.4) (2026-07-14)
54
77
 
55
-
56
78
  ### Features
57
79
 
58
80
  * configure Dependabot to target develop with auto-merge ([9e12b46](https://github.com/ManotLuijiu/pi-harness-runtime/commit/9e12b4620179b91d2dc1a4627e199920d3890f31))
@@ -60,7 +82,6 @@ All notable changes to this project will be documented in this file. See [standa
60
82
  * RFC-0052 skill-registry gap fixes + 40 new RFCs (0061-0100) ([6defd26](https://github.com/ManotLuijiu/pi-harness-runtime/commit/6defd26ff931fb9f93548647bf3a76098aa1d48b))
61
83
  * RFC-0060 memory-engine fixes + simplify auto-resume ([#52](https://github.com/ManotLuijiu/pi-harness-runtime/issues/52)) ([fb4de1a](https://github.com/ManotLuijiu/pi-harness-runtime/commit/fb4de1a9d1de74b875aabb58d0c06f7116994ca3))
62
84
 
63
-
64
85
  ### Bug Fixes
65
86
 
66
87
  * collapse duplicate version keys in workspace package.json files ([f3a7289](https://github.com/ManotLuijiu/pi-harness-runtime/commit/f3a728923a96308296e45d7ce3db8fe3ad36780a))
@@ -69,12 +90,10 @@ All notable changes to this project will be documented in this file. See [standa
69
90
 
70
91
  ### [0.9.1](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.7.1...v0.9.1) (2026-07-13)
71
92
 
72
-
73
93
  ### Features
74
94
 
75
95
  * **release:** add synced-workspace release script ([617649d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/617649d0b2227ebb4355eec4979b0c65822d60e3))
76
96
 
77
-
78
97
  ### Bug Fixes
79
98
 
80
99
  * publish missing runtime modules ([3fc7058](https://github.com/ManotLuijiu/pi-harness-runtime/commit/3fc70586d0cd10f4d38eab67e4cadaec20aaeb56))
@@ -82,19 +101,16 @@ All notable changes to this project will be documented in this file. See [standa
82
101
 
83
102
  ## [0.9.0](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.7.1...v0.9.0) (2026-07-13)
84
103
 
85
-
86
104
  ### Features
87
105
 
88
106
  * **release:** add synced-workspace release script ([617649d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/617649d0b2227ebb4355eec4979b0c65822d60e3))
89
107
 
90
-
91
108
  ### Bug Fixes
92
109
 
93
110
  * **release:** remove redundant Bump version step — was double-bumping version (0.7.1→0.8.0) when tag pushed ([413ad6d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/413ad6d49356ec62b94e9a4e09d3475ef39bebd6))
94
111
 
95
112
  ### [0.7.1](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.6.3...v0.7.1) (2026-07-13)
96
113
 
97
-
98
114
  ### Bug Fixes
99
115
 
100
116
  * **release:** set make_latest=true so new releases auto-mark as Latest ([26a7bb9](https://github.com/ManotLuijiu/pi-harness-runtime/commit/26a7bb98f3e603a9d68cbf39ca1558bc579d1a4a))
package/README.md CHANGED
@@ -191,9 +191,26 @@ bun test # 131+ tests passing
191
191
 
192
192
  ## Automatic Quota Fetching
193
193
 
194
- Quota data is automatically fetched from MiniMax console every 5 minutes:
194
+ Quota data is automatically surfaced per-provider:
195
195
 
196
- 1. **Setup cookies** (one-time):
196
+ | Provider | Source | Cadence |
197
+ |---|---|---|
198
+ | MiniMax | Playwright + cookies (continuous scrape) | every ~15 min |
199
+ | OpenAI / OpenAI Codex | TUI message capture (signal on limit) | on first limit hit |
200
+ | GLM (Z.ai / Zhipu) | TUI message capture (signal on limit) | on first limit hit |
201
+ | Anthropic | TUI message capture (signal on limit) | on first limit hit |
202
+ | OpenRouter | TUI message capture (signal on limit) | on first limit hit |
203
+ | Other / unknown | (no data — footer shows hint) | — |
204
+
205
+ The footer line carries the provider label, so you always know which LLM the data is from:
206
+
207
+ ```
208
+ MiniMax: 5h: 92% left · week: 80% left # continuous (scrape)
209
+ OpenAI: 5h: -- · week: -- (no signal yet) # pending first limit hit
210
+ GLM: limit hit (tokens), reset 2026-07-23T17:28:00Z # TUI signal observed
211
+ ```
212
+
213
+ 1. **MiniMax setup** (one-time):
197
214
 
198
215
  ```bash
199
216
  # Install EditThisCookie Chrome extension
@@ -202,13 +219,33 @@ Quota data is automatically fetched from MiniMax console every 5 minutes:
202
219
  # Save to ~/.config/minimax-cookies.txt
203
220
  ```
204
221
 
205
- 2. **Environment** (optional):
222
+ **Recommended:** drop the file into the dedicated cookie folder. The runtime
223
+ normalizes any format (Netscape or EditThisCookie JSON, any filename) on its own:
224
+
225
+ ```bash
226
+ # Save cookies as any name, any format into:
227
+ mkdir -p ~/.pi-harness-runtime/cookies
228
+ cp ~/Downloads/platform.minimax.io_cookies.txt ~/.pi-harness-runtime/cookies/
229
+ # …or use the EditThisCookie extension export (JSON) — same folder works.
230
+ ```
231
+
232
+ The `cookie-sanitizer` package (`@pi-harness/cookie-sanitizer`) runs chokidar v5 on this
233
+ folder, normalizes everything to standard Netscape, and writes the canonical cache at
234
+ `~/.config/minimax-cookies.txt`. Live update latency: <1 second after drop.
235
+
236
+ 2. **Other providers**: no setup needed. The runtime reads messages from the active
237
+ session and captures quota-exhaustion signals from the TUI. Once any limit is hit
238
+ (e.g. `OpenAI: context length exceeded, reset in 3 hr 27 min`), the footer updates
239
+ immediately and the data persists across `~/.pi/usage-status/mirror.json`.
240
+
241
+ 3. **Environment** (optional override):
206
242
 
207
243
  ```bash
208
- export QUOTA_COOKIE_FILE=~/.config/minimax-cookies.txt
244
+ export QUOTA_COOKIE_FILE=/path/to/your/cookies.txt
245
+ # When set, the sanitizer is bypassed and the scraper reads this file directly.
209
246
  ```
210
247
 
211
- 3. **Done!** Quota auto-refreshes every 5 minutes
248
+ 4. **Done!** Quota auto-refreshes every 5 minutes. Cookie file changes sync within ~1 second.
212
249
 
213
250
  ## Roadmap
214
251
 
package/footer-status.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { MirrorRecord } from "./mirror.ts";
1
+ import type { MirrorRecord, ProviderMirrorRecord } from "./mirror.ts";
2
2
  import { formatQuotaStatus } from "./harness/e2e/quota-status.js";
3
3
  import {
4
4
  parseContextWindowStatusLine,
@@ -39,29 +39,126 @@ export function parseFooterStatusValue(value: string): ParsedFooterStatusValue {
39
39
  return { kind: "unknown", value: null };
40
40
  }
41
41
 
42
+ /** Display name for a provider id. Used as the footer label. */
43
+ function providerDisplayName(provider: string | null | undefined): string {
44
+ if (!provider) return "Provider";
45
+ switch (provider) {
46
+ case "minimax":
47
+ return "MiniMax";
48
+ case "openai":
49
+ return "OpenAI";
50
+ case "openai-codex":
51
+ return "OpenAI Codex";
52
+ case "glm":
53
+ return "GLM";
54
+ case "anthropic":
55
+ return "Anthropic";
56
+ case "openrouter":
57
+ return "OpenRouter";
58
+ default:
59
+ return provider;
60
+ }
61
+ }
62
+
63
+ /** Hint shown when the provider has no usage data yet. */
64
+ function missingDataHint(
65
+ provider: string | null | undefined,
66
+ hasCookieSource: boolean,
67
+ ): string {
68
+ if (provider === "minimax") {
69
+ return "drop minimax cookies into ~/.pi-harness-runtime/cookies/";
70
+ }
71
+ if (provider === "openai-codex") {
72
+ return "will update on first TUI limit signal";
73
+ }
74
+ if (
75
+ provider === "openai" ||
76
+ provider === "glm" ||
77
+ provider === "anthropic" ||
78
+ provider === "openrouter"
79
+ ) {
80
+ return "no signal yet (updates on first limit hit)";
81
+ }
82
+ if (!hasCookieSource) return "no usage source configured";
83
+ return "no data yet";
84
+ }
85
+
86
+ /** Normalize an unknown mirror record into a ProviderMirrorRecord view. */
87
+ function toProviderView(
88
+ mirror: MirrorRecord | ProviderMirrorRecord | null,
89
+ ): ProviderMirrorRecord | null {
90
+ if (!mirror) return null;
91
+ const m = mirror as ProviderMirrorRecord;
92
+ if (typeof m.provider === "string") return m;
93
+ // Legacy flat-shape: already a single record without a provider wrapper.
94
+ return {
95
+ synced_at: m.synced_at ?? new Date().toISOString(),
96
+ provider: (m as MirrorRecord).provider ?? "",
97
+ source: "scrape",
98
+ model: (m as MirrorRecord).model,
99
+ h5_used_pct: (m as MirrorRecord).h5_used_pct,
100
+ h5_resets_at: (m as MirrorRecord).h5_resets_at,
101
+ weekly_used_pct: (m as MirrorRecord).weekly_used_pct,
102
+ weekly_resets_at: (m as MirrorRecord).weekly_resets_at,
103
+ };
104
+ }
105
+
42
106
  export function buildFooterStatusValue(
43
107
  local: { today: { tokens: number; cost: number } },
44
- mirror: MirrorRecord | null,
108
+ mirror: MirrorRecord | ProviderMirrorRecord | null,
45
109
  freshness: "fresh" | "stale" | "expired" | "missing",
110
+ hasCookieSource: boolean = true,
111
+ activeProvider: string | null = null,
46
112
  ): string {
113
+ const view = toProviderView(mirror);
114
+ const provider = activeProvider ?? view?.provider ?? null;
115
+ const label = providerDisplayName(provider);
116
+
117
+ // Continuous data path: a fresh record has 5h or weekly percentages.
47
118
  if (
48
- mirror &&
119
+ view &&
49
120
  freshness !== "expired" &&
50
- (mirror.h5_used_pct !== undefined || mirror.weekly_used_pct !== undefined)
121
+ (view.h5_used_pct !== undefined || view.weekly_used_pct !== undefined)
51
122
  ) {
52
123
  const quotaStatus = formatQuotaStatus({
53
- provider: "minimax",
54
- h5UsedPct: mirror.h5_used_pct ?? 0,
55
- h5ResetsAt: mirror.h5_resets_at,
56
- weeklyUsedPct: mirror.weekly_used_pct ?? 0,
57
- weeklyResetsAt: mirror.weekly_resets_at,
58
- scrapedAt: mirror.synced_at,
124
+ provider: (provider ?? "minimax") as "minimax",
125
+ h5UsedPct: view.h5_used_pct ?? 0,
126
+ h5ResetsAt: view.h5_resets_at,
127
+ weeklyUsedPct: view.weekly_used_pct ?? 0,
128
+ weeklyResetsAt: view.weekly_resets_at,
129
+ scrapedAt: view.synced_at,
59
130
  });
60
131
  const freshnessSuffix =
61
132
  freshness === "fresh" || freshness === "missing" ? "" : ` · ${freshness}`;
62
- return `${quotaStatus.extended}${freshnessSuffix}`;
133
+ return `${label}: ${quotaStatus.extended}${freshnessSuffix}`;
134
+ }
135
+
136
+ // TUI-signal path: an exhausted record from a limit hit.
137
+ if (
138
+ view &&
139
+ view.exhausted &&
140
+ (view.limitType !== undefined || view.resets_at !== undefined)
141
+ ) {
142
+ const reset = view.resets_at ?? view.h5_resets_at ?? "soon";
143
+ return `${label}: limit hit (${view.limitType ?? "tokens"}), reset ${reset}`;
144
+ }
145
+
146
+ // Discoverable hint when we have no data yet.
147
+ if (
148
+ provider &&
149
+ (!view || freshness === "expired" || freshness === "missing")
150
+ ) {
151
+ const hint = missingDataHint(provider, hasCookieSource);
152
+ // MiniMax-on-fresh-machine still gets the cookie hint; for others
153
+ // the hint explains the signal-driven design.
154
+ if (provider === "minimax" && !hasCookieSource) {
155
+ return `${label}: 5h: -- (${hint})`;
156
+ }
157
+ return `${label}: 5h: -- · week: -- (${hint})`;
63
158
  }
64
159
 
160
+ // Silent fallback: only reached if we genuinely don't know the provider.
161
+ // No "5h/week: --" line here because we'd be making up data.
65
162
  const todayStr = `${(local.today.tokens / 1000).toFixed(1)}k tok · $${local.today.cost.toFixed(3)}`;
66
163
  return `today: ${todayStr}`;
67
164
  }
@@ -10,7 +10,7 @@
10
10
  * - Capture API responses for usage data
11
11
  */
12
12
 
13
- import { existsSync, readFileSync } from "node:fs";
13
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
14
14
  import { join } from "node:path";
15
15
  import { homedir } from "node:os";
16
16
  import { parseMiniMaxQuotaText } from "./minimax-quota-parser.js";
@@ -385,10 +385,19 @@ export class MiniMaxQuotaScraper {
385
385
  throw new Error(msg);
386
386
  }
387
387
 
388
+ // Pre-load the canonical cache so that the runtime-owned file
389
+ // (written by packages/cookie-sanitizer) takes precedence over
390
+ // whatever `this.config.cookieFile` points at. Both eventually
391
+ // resolve to the same standard Netscape format.
388
392
  const cookies = loadNetscapeCookies(this.config.cookieFile!);
389
393
 
390
394
  if (cookies.length === 0) {
391
- const msg = `No cookies found in ${this.config.cookieFile}\nExport cookies from browser in Netscape format.`;
395
+ const dropHint = join(homedir(), ".pi-harness-runtime", "cookies");
396
+ const msg =
397
+ `No cookies found.\n` +
398
+ `Drop your platform.minimax.io cookies (Netscape or EditThisCookie JSON) into:\n` +
399
+ ` ${dropHint}\n` +
400
+ `…or run: bun packages/auth/src/run-minimax-auth.ts auth`;
392
401
  if (!this.config.quiet) console.error("[MiniMaxQuotaScraper] " + msg);
393
402
  throw new Error(msg);
394
403
  }
@@ -474,10 +483,43 @@ export class MiniMaxQuotaScraper {
474
483
  }
475
484
 
476
485
  /**
477
- * Check if cookie file exists
486
+ * Check if any cookie source exists.
487
+ *
488
+ * "Source" can be either:
489
+ * - the canonical cache (this.config.cookieFile) — runtime-owned,
490
+ * populated by packages/cookie-sanitizer
491
+ * - the user-facing drop folder — ~/.pi-harness-runtime/cookies/
492
+ * — populated by humans (any name, any format)
493
+ *
494
+ * Either is enough to attempt the scrape.
478
495
  */
479
496
  hasCookieFile(): boolean {
480
- return existsSync(this.config.cookieFile!);
497
+ // Canonical cache present?
498
+ if (existsSync(this.config.cookieFile!)) return true;
499
+
500
+ // Drop folder has anything readable? (Sync would normalize it on the next trigger.)
501
+ try {
502
+ const dropDir = join(homedir(), ".pi-harness-runtime", "cookies");
503
+ if (existsSync(dropDir)) {
504
+ const entries = readdirSync(dropDir);
505
+ if (entries.length > 0) return true;
506
+ // One-level walk into subfolders.
507
+ for (const name of entries) {
508
+ try {
509
+ if (statSync(join(dropDir, name)).isDirectory()) {
510
+ const sub = readdirSync(join(dropDir, name));
511
+ if (sub.length > 0) return true;
512
+ }
513
+ } catch {
514
+ // ignore unreadable sub-entry
515
+ }
516
+ }
517
+ }
518
+ } catch {
519
+ // best-effort; treat as no
520
+ }
521
+
522
+ return false;
481
523
  }
482
524
 
483
525
  /**