feat: show fast mode in minimal footer

This commit is contained in:
叶林立
2026-08-19 22:24:37 +08:00
parent 3cdcc3510b
commit 5c4d6c6a87
15 changed files with 1340 additions and 14 deletions
+5 -2
View File
@@ -36,9 +36,12 @@
- 初始导入快照:`e928179a3b35e8fe5837878aed071d6025ced45c``v1.33.0`)。 - 初始导入快照:`e928179a3b35e8fe5837878aed071d6025ced45c``v1.33.0`)。
- 官方 npm tarball 不包含 Pi Extension;仓库保留官方 README、LICENSE 和来源说明,匹配 CLI 版本由 `config/hippo-memory-version` 声明。 - 官方 npm tarball 不包含 Pi Extension;仓库保留官方 README、LICENSE 和来源说明,匹配 CLI 版本由 `config/hippo-memory-version` 声明。
- `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程 compaction,默认阈值为 90%。 - `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程 compaction,默认阈值为 90%。
- `@ogulcancelik/pi-minimal-footer@0.1.10`:用紧凑的上下文仪表订阅用量条替换默认 footer - `pi-minimal-footer/`:基于上游紧凑 footer,保留上下文仪表订阅用量条和扩展状态,并在 Codex Fast 模式开启时显示 `Fast on`
- 上游来源:<https://github.com/ogulcancelik/pi-extensions/tree/main/packages/pi-minimal-footer>
- 初始导入快照:`77bd1e175003cd08e6d05d9e7fed695f86ae87b7``@ogulcancelik/pi-minimal-footer@0.1.10`)。
- 该目录从明确 commit 的上游源码导入并由本仓库直接维护,不使用 npm 预编译产物,不是 submodule,也不保留嵌套 `.git`
- `pi-condense@2.9.1`:总结已完成的工具调用批次,以短 stub 替换历史原始输出,并通过 `context_tree_query` 按需恢复;组合包在用户尚未配置 `contextPrune.enabled` 时默认开启。 - `pi-condense@2.9.1`:总结已完成的工具调用批次,以短 stub 替换历史原始输出,并通过 `context_tree_query` 按需恢复;组合包在用户尚未配置 `contextPrune.enabled` 时默认开启。
- 根包还固定安装 `@tavily/pi-extension@0.1.2``@keenable/pi-search@0.1.2``pi-hashline-edit@0.8.3``typescript-language-server@5.3.0``typescript@6.0.3``pi-context-view``@firstpick/pi-extension-codex-fast-mode``@gotgenes/pi-permission-system``pi-tool-search` 分别指向仓库内对应目录的本地 `file:` 依赖。 - 根包还固定安装 `@tavily/pi-extension@0.1.2``@keenable/pi-search@0.1.2``pi-hashline-edit@0.8.3``typescript-language-server@5.3.0``typescript@6.0.3``pi-context-view``@firstpick/pi-extension-codex-fast-mode``@gotgenes/pi-permission-system``pi-minimal-footer``pi-tool-search` 分别指向仓库内对应目录的本地 `file:` 依赖。
- `install.sh`:先安装根组合包,再交互检查 Hippo Memory CLI、Kitty/Solarized Dark、Oh My Zsh/Powerlevel10k/Zsh 插件、CodeGraph、Kotlin LSP、Java 21+ 和 JDT LS;缺失项目只在用户明确选择 `Y` 后安装或配置。Hippo 项目目录与 `hippo init` 始终由用户自行决定。Powerlevel10k 默认配置来自仓库内置的 `config/p10k.zsh`(当前 Rainbow/ASCII 单行紧凑主题)。 - `install.sh`:先安装根组合包,再交互检查 Hippo Memory CLI、Kitty/Solarized Dark、Oh My Zsh/Powerlevel10k/Zsh 插件、CodeGraph、Kotlin LSP、Java 21+ 和 JDT LS;缺失项目只在用户明确选择 `Y` 后安装或配置。Hippo 项目目录与 `hippo init` 始终由用户自行决定。Powerlevel10k 默认配置来自仓库内置的 `config/p10k.zsh`(当前 Rainbow/ASCII 单行紧凑主题)。
- `update.sh`:先通过 `pi update` 升级根组合包,再只升级当前已安装的终端环境和机器级依赖;未安装项直接跳过。升级前先查询并比较本地与远端版本,只有版本不同时才下载或替换;Powerlevel10k 配置优先从 `pi update` 后的已安装组合包读取,并与 `.zshrc` 受管块一起按内容比较后增量同步。 - `update.sh`:先通过 `pi update` 升级根组合包,再只升级当前已安装的终端环境和机器级依赖;未安装项直接跳过。升级前先查询并比较本地与远端版本,只有版本不同时才下载或替换;Powerlevel10k 配置优先从 `pi update` 后的已安装组合包读取,并与 `.zshrc` 受管块一起按内容比较后增量同步。
- `uninstall.sh`:先移除根组合包;若检测到 Hippo Memory CLI,再明确询问是否卸载确认属于 npm 全局安装的 `hippo-memory`,默认保留且始终不删除 `.hippo/` 或用户记忆数据。其他共享终端环境和机器工具不卸载。 - `uninstall.sh`:先移除根组合包;若检测到 Hippo Memory CLI,再明确询问是否卸载确认属于 npm 全局安装的 `hippo-memory`,默认保留且始终不删除 `.hippo/` 或用户记忆数据。其他共享终端环境和机器工具不卸载。
+1 -1
View File
@@ -13,7 +13,7 @@
- `pi-lsp@0.1.7`:为 TypeScript/JavaScript、Kotlin 和 Java 提供 LSP 诊断、跳转与符号工具;TypeScript 后端由组合包内置。 - `pi-lsp@0.1.7`:为 TypeScript/JavaScript、Kotlin 和 Java 提供 LSP 诊断、跳转与符号工具;TypeScript 后端由组合包内置。
- `hippo-memory-pi/`:从官方 `hippo-memory v1.33.0` 源码导入的 Pi Extension,提供项目记忆注入、错误捕获和 sleep consolidation。 - `hippo-memory-pi/`:从官方 `hippo-memory v1.33.0` 源码导入的 Pi Extension,提供项目记忆注入、错误捕获和 sleep consolidation。
- `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程压缩。 - `@ogulcancelik/pi-codex-compaction@0.1.3`:为 `openai-codex` 提供原生远程压缩。
- `@ogulcancelik/pi-minimal-footer@0.1.10`用紧凑的上下文仪表和订阅用量条替换默认 footer。 - 本仓库维护的 `pi-minimal-footer`:基于 `@ogulcancelik/pi-minimal-footer@0.1.10`用紧凑的上下文仪表和订阅用量条替换默认 footer,并在 Codex Fast 模式开启时显示 `Fast on`
- `pi-condense@2.9.1`:把已完成的工具调用批次总结为可恢复的短摘要,并通过 `context_tree_query` 按需取回原始输出;组合包首次加载时默认开启。 - `pi-condense@2.9.1`:把已完成的工具调用批次总结为可恢复的短摘要,并通过 `context_tree_query` 按需取回原始输出;组合包首次加载时默认开启。
- 本仓库维护的 `pi-tool-search`:从完整工具定义生成并缓存经过校验的工作流分组,以最多 3 个动态组的 LRU 策略按组加载原始完整 schema。 - 本仓库维护的 `pi-tool-search`:从完整工具定义生成并缓存经过校验的工作流分组,以最多 3 个动态组的 LRU 策略按组加载原始完整 schema。
- 本仓库维护的 `pi-permission-auto-review`:作为 `pi-permission-system` authorizer,使用 Codex Guardian 风格策略自动复核 `ask` 请求。 - 本仓库维护的 `pi-permission-auto-review`:作为 `pi-permission-system` authorizer,使用 Codex Guardian 风格策略自动复核 `ask` 请求。
+11 -8
View File
@@ -13,7 +13,7 @@
"@gotgenes/pi-permission-system": "file:./pi-permission-system", "@gotgenes/pi-permission-system": "file:./pi-permission-system",
"@keenable/pi-search": "0.1.2", "@keenable/pi-search": "0.1.2",
"@ogulcancelik/pi-codex-compaction": "0.1.3", "@ogulcancelik/pi-codex-compaction": "0.1.3",
"@ogulcancelik/pi-minimal-footer": "0.1.10", "@ogulcancelik/pi-minimal-footer": "file:./pi-minimal-footer",
"@tavily/pi-extension": "0.1.2", "@tavily/pi-extension": "0.1.2",
"context-mode": "1.0.169", "context-mode": "1.0.169",
"pi-condense": "2.9.1", "pi-condense": "2.9.1",
@@ -2690,13 +2690,8 @@
} }
}, },
"node_modules/@ogulcancelik/pi-minimal-footer": { "node_modules/@ogulcancelik/pi-minimal-footer": {
"version": "0.1.10", "resolved": "pi-minimal-footer",
"resolved": "https://registry.npmjs.org/@ogulcancelik/pi-minimal-footer/-/pi-minimal-footer-0.1.10.tgz", "link": true
"integrity": "sha512-JXR2drsf7Igx12tcSFneERnCD2f1Cfz8GwWGJx9T4//sMc5Uj4lu66wzQfb+8hd24T9c8XmQFWHuAd997E041Q==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
}, },
"node_modules/@oxc-project/types": { "node_modules/@oxc-project/types": {
"version": "0.144.0", "version": "0.144.0",
@@ -7030,6 +7025,14 @@
"zod": "^3.25.28 || ^4" "zod": "^3.25.28 || ^4"
} }
}, },
"pi-minimal-footer": {
"name": "@ogulcancelik/pi-minimal-footer",
"version": "0.1.10-my-pi.1",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"pi-permission-system": { "pi-permission-system": {
"name": "@gotgenes/pi-permission-system", "name": "@gotgenes/pi-permission-system",
"version": "26.2.1", "version": "26.2.1",
+3 -2
View File
@@ -12,6 +12,7 @@
"extensions", "extensions",
"hippo-memory-pi", "hippo-memory-pi",
"pi-rtk-optimizer", "pi-rtk-optimizer",
"pi-minimal-footer",
"pi-tool-search/extensions", "pi-tool-search/extensions",
"pi-tool-search/docs", "pi-tool-search/docs",
"pi-tool-search/package.json", "pi-tool-search/package.json",
@@ -51,7 +52,7 @@
"./node_modules/@firstpick/pi-extension-codex-fast-mode/index.ts", "./node_modules/@firstpick/pi-extension-codex-fast-mode/index.ts",
"./hippo-memory-pi/index.ts", "./hippo-memory-pi/index.ts",
"./node_modules/@ogulcancelik/pi-codex-compaction/index.ts", "./node_modules/@ogulcancelik/pi-codex-compaction/index.ts",
"./node_modules/@ogulcancelik/pi-minimal-footer/index.ts", "./pi-minimal-footer/index.ts",
"./extensions/condense.ts", "./extensions/condense.ts",
"./pi-permission-auto-review/index.ts", "./pi-permission-auto-review/index.ts",
"./extensions/permission-system.ts", "./extensions/permission-system.ts",
@@ -69,7 +70,7 @@
"@gotgenes/pi-permission-system": "file:./pi-permission-system", "@gotgenes/pi-permission-system": "file:./pi-permission-system",
"@keenable/pi-search": "0.1.2", "@keenable/pi-search": "0.1.2",
"@ogulcancelik/pi-codex-compaction": "0.1.3", "@ogulcancelik/pi-codex-compaction": "0.1.3",
"@ogulcancelik/pi-minimal-footer": "0.1.10", "@ogulcancelik/pi-minimal-footer": "file:./pi-minimal-footer",
"@tavily/pi-extension": "0.1.2", "@tavily/pi-extension": "0.1.2",
"context-mode": "1.0.169", "context-mode": "1.0.169",
"pi-condense": "2.9.1", "pi-condense": "2.9.1",
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Can Celik
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+75
View File
@@ -0,0 +1,75 @@
# pi-minimal-footer
Minimal footer for [pi](https://github.com/earendil-works/pi) that replaces the default footer with a compact two-line display: context gauge on top, subscription usage bars below.
![Claude Max](assets/claude.png)
![OpenAI Codex](assets/codex.png)
## Features
- **Context gauge** — optional working directory and git branch, model, thinking level, and context window usage with token counts
- **Subscription usage bars** — rolling window quotas with reset timers for supported providers
- **Auto-refresh** — fetches usage on startup and model switch, then every 5 minutes
- **Git integration** — branch name, dirty state, ahead/behind counts
- **Extension statuses** — preserves statuses published through `ctx.ui.setStatus()` and shows `Fast on` while Codex Fast mode is enabled
## Supported providers
| Provider | What it shows |
| -------------- | ------------------------------------------------------ |
| Claude Max | 5h + weekly rolling windows |
| OpenAI Codex | Primary + secondary rolling windows |
| GitHub Copilot | Premium interactions + chat quotas |
| Google Gemini | Pro + Flash remaining quotas |
| MiniMax | 5h + weekly rolling windows (Token Plan, credit-based) |
| MiniMax CN | Same as MiniMax, China endpoint |
| Kimi Coding | 5h + weekly rolling windows (Plan) |
## Install
```bash
pi install npm:@ogulcancelik/pi-minimal-footer
```
## Configuration
Environment variables (all optional):
| Variable | Description | Default |
| ------------------------------- | -------------------------------------------------------- | ------- |
| `PI_MINIMAL_FOOTER_SHOW_CWD` | Show current working directory in footer status line | `1` |
| `PI_MINIMAL_FOOTER_SHOW_BRANCH` | Show git branch/dirty/ahead/behind in footer status line | `1` |
Accepted false values: `0`, `false`, `no`, `off` (case-insensitive).
## How it works
The footer reads context usage from the last assistant message's token counts (free — comes with every LLM response). Subscription usage is fetched from each provider's dedicated quota API using your existing auth tokens from `~/.pi/agent/auth.json` or environment variables.
Usage is fetched:
- Once on startup
- Immediately on model switch (Ctrl+P)
- Every 5 minutes after that
Git state is refreshed:
- Once on startup
- When pi reports a branch change
- At the end of each turn
The footer adapts to narrow terminals by stacking lines vertically instead of the single-line wide layout.
## Known issues
### Claude Max usage bar not showing
Anthropic's OAuth usage endpoint (`/api/oauth/usage`) has been returning persistent 429 (rate limit) errors since late March 2026, affecting all third-party tools that display Claude usage data (CodexBar, oh-my-claudecode, claude-pulse, etc.). This is an Anthropic-side issue — tracked in [claude-code#30930](https://github.com/anthropics/claude-code/issues/30930) and [claude-code#31021](https://github.com/anthropics/claude-code/issues/31021). The usage bar will start working again once Anthropic fixes the endpoint.
## Notes
- Replaces the default pi footer entirely via `ctx.ui.setFooter()`
- The local `my-pi` version reads `footerData.getExtensionStatuses()`; `codex-fast-mode=on` is rendered as an accented `Fast on` segment, while the disabled state is hidden.
- Auth tokens are read from `~/.pi/agent/auth.json` (populated by `/login`) or standard env vars (`ANTHROPIC_API_KEY`, `MINIMAX_API_KEY`, etc.)
- Providers without auth simply don't show a usage bar — no errors
+8
View File
@@ -0,0 +1,8 @@
# Upstream source
- Repository: <https://github.com/ogulcancelik/pi-extensions/tree/main/packages/pi-minimal-footer>
- Imported commit: `77bd1e175003cd08e6d05d9e7fed695f86ae87b7`
- Imported package version: `@ogulcancelik/pi-minimal-footer@0.1.10`
- Imported on: 2026-08-19
This directory is maintained directly by `my-pi`; it is not a submodule and does not retain upstream Git metadata. Local changes add rendering for Pi extension statuses, including an explicit `Fast on` indicator for `@firstpick/pi-extension-codex-fast-mode`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large Load Diff
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@ogulcancelik/pi-minimal-footer",
"version": "0.1.10-my-pi.1",
"description": "Minimal footer for pi with context gauge and subscription usage bars",
"type": "module",
"keywords": [
"pi-package",
"pi-extension",
"footer",
"context",
"usage",
"coding-agent"
],
"author": "Can Celik",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/ogulcancelik/pi-extensions.git",
"directory": "packages/pi-minimal-footer"
},
"bugs": {
"url": "https://github.com/ogulcancelik/pi-extensions/issues"
},
"homepage": "https://github.com/ogulcancelik/pi-extensions/tree/main/packages/pi-minimal-footer#readme",
"engines": {
"node": ">=18.0.0"
},
"pi": {
"extensions": [
"./index.ts"
]
},
"files": [
"index.ts",
"status.ts",
"assets/claude.png",
"assets/codex.png",
"README.md",
"UPSTREAM.md",
"LICENSE"
],
"scripts": {
"test": "node --experimental-strip-types --test test/*.test.ts",
"check": "node --experimental-strip-types --check index.ts"
}
}
+28
View File
@@ -0,0 +1,28 @@
export const FAST_MODE_STATUS_KEY = "codex-fast-mode";
export interface FooterStatusItem {
text: string;
accent: boolean;
}
/** Convert Pi extension statuses into compact footer segments. */
export function collectFooterStatusItems(
statuses: ReadonlyMap<string, string>,
): FooterStatusItem[] {
const items: FooterStatusItem[] = [];
for (const [key, value] of statuses) {
if (key === FAST_MODE_STATUS_KEY) {
if (value.trim().toLowerCase() === "on") {
items.push({ text: "Fast on", accent: true });
}
continue;
}
if (value.trim()) {
items.push({ text: value, accent: false });
}
}
return items;
}
+29
View File
@@ -0,0 +1,29 @@
import assert from "node:assert/strict";
import test from "node:test";
import { collectFooterStatusItems, FAST_MODE_STATUS_KEY } from "../status.ts";
test("renders enabled Codex Fast mode as an explicit accented segment", () => {
const items = collectFooterStatusItems(new Map([[FAST_MODE_STATUS_KEY, "on"]]));
assert.deepEqual(items, [{ text: "Fast on", accent: true }]);
});
test("hides disabled Codex Fast mode", () => {
const items = collectFooterStatusItems(new Map([[FAST_MODE_STATUS_KEY, "off"]]));
assert.deepEqual(items, []);
});
test("preserves other non-empty extension statuses", () => {
const items = collectFooterStatusItems(new Map([
["one", "Ready"],
["empty", " "],
["two", "2 tasks"],
]));
assert.deepEqual(items, [
{ text: "Ready", accent: false },
{ text: "2 tasks", accent: false },
]);
});
+5
View File
@@ -18,6 +18,11 @@ test("all package extensions load together without global registration conflicts
packageJson.pi.extensions.includes("./hippo-memory-pi/index.ts"), packageJson.pi.extensions.includes("./hippo-memory-pi/index.ts"),
"the bundle must load the vendored official Hippo Memory extension", "the bundle must load the vendored official Hippo Memory extension",
); );
assert.ok(
packageJson.pi.extensions.includes("./pi-minimal-footer/index.ts"),
"the bundle must load the locally maintained minimal footer",
);
assert.equal(packageJson.dependencies["@ogulcancelik/pi-minimal-footer"], "file:./pi-minimal-footer");
assert.equal( assert.equal(
packageJson.pi.extensions.some((entry) => entry.includes("pi-hermes-memory") || entry.includes("the-forge-flow")), packageJson.pi.extensions.some((entry) => entry.includes("pi-hermes-memory") || entry.includes("the-forge-flow")),
false, false,
+1 -1
View File
@@ -46,7 +46,7 @@ test("the package has exactly one local MCP adapter owner", async () => {
const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as { const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as {
pi: { extensions: string[] }; pi: { extensions: string[] };
}; };
const localEntries = packageJson.pi.extensions.filter((entry) => entry.startsWith("./extensions/")); const localEntries = packageJson.pi.extensions.filter((entry) => !entry.startsWith("./node_modules/"));
const adapterOwners: string[] = []; const adapterOwners: string[] = [];
for (const entry of localEntries) { for (const entry of localEntries) {
const source = await readFile(new URL(`../${entry.slice(2)}`, import.meta.url), "utf8"); const source = await readFile(new URL(`../${entry.slice(2)}`, import.meta.url), "utf8");