pi-harness-runtime 0.9.7 → 0.9.11

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 +24 -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,19 @@
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.11](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.8...v0.9.11) (2026-07-23)
6
+
7
+ ### Bug Fixes
8
+
9
+ * **release:** test workflow trigger via manual version bump
10
+ * **tests:** update footer-status expectations for provider label prefix
11
+ * **tests:** mirror round-trip expects per-provider shape + source field
6
12
 
13
+ ### [0.9.8](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.5...v0.9.8) (2026-07-23)
14
+
15
+ ### Features
16
+
17
+ * **cookie-sanitizer:** discoverable drop folder + per-provider mirror ([772eda5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/772eda5c5c698572a1cef2dc93df0278e9a380ee))
7
18
 
8
19
  ### Bug Fixes
9
20
 
@@ -12,6 +23,18 @@ All notable changes to this project will be documented in this file. See [standa
12
23
 
13
24
  ### [0.9.3](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.5.0-beta.1...v0.9.3) (2026-07-14)
14
25
 
26
+ ### Bug Fixes
27
+
28
+ * 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))
29
+
30
+ ### [0.9.7](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.5...v0.9.7) (2026-07-16)
31
+
32
+ ### Bug Fixes
33
+
34
+ * harden auto-resume after compaction ([13378e8](https://github.com/ManotLuijiu/pi-harness-runtime/commit/13378e852c984ead0e66f0adeffc073ff58af50c))
35
+ * resume after output-limit error stops ([8c75a24](https://github.com/ManotLuijiu/pi-harness-runtime/commit/8c75a2432082c4f9ba6296ffe2d04b636863bf2f))
36
+
37
+ ### [0.9.3](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.5.0-beta.1...v0.9.3) (2026-07-14)
15
38
 
16
39
  ### Bug Fixes
17
40
 
@@ -19,21 +42,18 @@ All notable changes to this project will be documented in this file. See [standa
19
42
 
20
43
  ### [0.9.6](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.5...v0.9.6) (2026-07-16)
21
44
 
22
-
23
45
  ### Bug Fixes
24
46
 
25
47
  * harden auto-resume after compaction ([13378e8](https://github.com/ManotLuijiu/pi-harness-runtime/commit/13378e852c984ead0e66f0adeffc073ff58af50c))
26
48
 
27
49
  ### [0.9.3](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.5.0-beta.1...v0.9.3) (2026-07-14)
28
50
 
29
-
30
51
  ### Bug Fixes
31
52
 
32
53
  * 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
54
 
34
55
  ### [0.9.5](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.4...v0.9.5) (2026-07-15)
35
56
 
36
-
37
57
  ### Features
38
58
 
39
59
  * add 15 packages from subagent batch + test expansions ([c82d898](https://github.com/ManotLuijiu/pi-harness-runtime/commit/c82d898277779886cdd1f7f42087698762becde3))
@@ -45,14 +65,12 @@ All notable changes to this project will be documented in this file. See [standa
45
65
  * add package metadata for auth, scheduler, shared-context, token-estimation ([67e2621](https://github.com/ManotLuijiu/pi-harness-runtime/commit/67e26211633f01de4f0d5eca76abc4d40d1ee0ea))
46
66
  * flesh out RFC 71-75 specs + fix codex-adapter LSP types ([38b8643](https://github.com/ManotLuijiu/pi-harness-runtime/commit/38b86438ef45f75d1c34caf5b4f401d29c76d44d))
47
67
 
48
-
49
68
  ### Bug Fixes
50
69
 
51
70
  * auto-resume after compaction and output limits ([0cff2bc](https://github.com/ManotLuijiu/pi-harness-runtime/commit/0cff2bcf9035a699496e2726b74a1576863cf82e))
52
71
 
53
72
  ### [0.9.4](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.5.0-beta.1...v0.9.4) (2026-07-14)
54
73
 
55
-
56
74
  ### Features
57
75
 
58
76
  * configure Dependabot to target develop with auto-merge ([9e12b46](https://github.com/ManotLuijiu/pi-harness-runtime/commit/9e12b4620179b91d2dc1a4627e199920d3890f31))
@@ -60,7 +78,6 @@ All notable changes to this project will be documented in this file. See [standa
60
78
  * RFC-0052 skill-registry gap fixes + 40 new RFCs (0061-0100) ([6defd26](https://github.com/ManotLuijiu/pi-harness-runtime/commit/6defd26ff931fb9f93548647bf3a76098aa1d48b))
61
79
  * 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
80
 
63
-
64
81
  ### Bug Fixes
65
82
 
66
83
  * collapse duplicate version keys in workspace package.json files ([f3a7289](https://github.com/ManotLuijiu/pi-harness-runtime/commit/f3a728923a96308296e45d7ce3db8fe3ad36780a))
@@ -69,12 +86,10 @@ All notable changes to this project will be documented in this file. See [standa
69
86
 
70
87
  ### [0.9.1](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.7.1...v0.9.1) (2026-07-13)
71
88
 
72
-
73
89
  ### Features
74
90
 
75
91
  * **release:** add synced-workspace release script ([617649d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/617649d0b2227ebb4355eec4979b0c65822d60e3))
76
92
 
77
-
78
93
  ### Bug Fixes
79
94
 
80
95
  * publish missing runtime modules ([3fc7058](https://github.com/ManotLuijiu/pi-harness-runtime/commit/3fc70586d0cd10f4d38eab67e4cadaec20aaeb56))
@@ -82,19 +97,16 @@ All notable changes to this project will be documented in this file. See [standa
82
97
 
83
98
  ## [0.9.0](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.7.1...v0.9.0) (2026-07-13)
84
99
 
85
-
86
100
  ### Features
87
101
 
88
102
  * **release:** add synced-workspace release script ([617649d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/617649d0b2227ebb4355eec4979b0c65822d60e3))
89
103
 
90
-
91
104
  ### Bug Fixes
92
105
 
93
106
  * **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
107
 
95
108
  ### [0.7.1](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.6.3...v0.7.1) (2026-07-13)
96
109
 
97
-
98
110
  ### Bug Fixes
99
111
 
100
112
  * **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
  /**