pi-cc-extensions 0.1.2 → 0.1.5

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.
package/README.md CHANGED
@@ -1,162 +1,162 @@
1
- <p align="center">
2
- <img src="./assets/readme/hero.svg" width="100%" alt="pi-cc-extensions:为 Pi 提供 Claude Code 风格界面、上下文洞察和历史 Session 引用">
3
- </p>
4
-
5
- <p align="center">
6
- <a href="#安装"><img alt="Pi package" src="https://img.shields.io/badge/Pi-package-58B7FF?style=flat-square"></a>
7
- <a href="./package.json"><img alt="Version 0.1.1" src="https://img.shields.io/badge/version-0.1.1-66E3C4?style=flat-square"></a>
8
- <a href="#兼容性"><img alt="Node.js 22.19 or newer" src="https://img.shields.io/badge/Node.js-%E2%89%A522.19-66E3C4?style=flat-square"></a>
9
- <a href="./extensions"><img alt="TypeScript extensions" src="https://img.shields.io/badge/TypeScript-extensions-3178C6?style=flat-square"></a>
10
- </p>
11
-
12
- <p align="center">
13
- 为 Pi 补充一套更接近 Claude Code 的终端工作流 · 上下文洞察 · 历史 Session 引用
14
- </p>
15
-
16
- ---
17
-
18
- ## 截图
19
-
20
- <table>
21
- <tr>
22
- <td colspan="2" align="center">
23
- <img src="./assets/readme/welcome.webp" width="100%" alt="Pi 的 Welcome 界面,展示 Tips、已加载上下文、扩展和最近会话">
24
- <br>
25
- <sub><b>Welcome 界面</b><br>启动时查看已加载上下文、扩展与最近会话</sub>
26
- </td>
27
- </tr>
28
- <tr>
29
- <td width="50%" align="center">
30
- <img src="./assets/readme/showcase.webp" width="100%" alt="Pi 终端中的 Claude Code 风格扩展界面,包含工具调用摘要、Diff 预览和 Powerline 状态栏">
31
- <br>
32
- <sub><b>Claude Code 风格界面</b><br>工具调用摘要、Diff 预览与 Powerline 状态栏</sub>
33
- </td>
34
- <td width="50%" align="center">
35
- <img src="./assets/readme/session-reference.webp" width="100%" alt="在 Pi 中通过 @ 补全选择历史 Session">
36
- <br>
37
- <sub><b>历史 Session 引用</b><br>通过 @ 补全快速找到并引用历史会话</sub>
38
- </td>
39
- </tr>
40
- <tr>
41
- <td width="50%" align="center">
42
- <img src="./assets/readme/context-usage.webp" width="100%" alt="Pi 上下文窗口用量查看器">
43
- <br>
44
- <sub><b>上下文用量</b><br>查看 System prompt、Tools、Skills 等占用分布</sub>
45
- </td>
46
- <td width="50%" align="center">
47
- <img src="./assets/readme/context-preview.webp" width="100%" alt="Pi 中展开查看 System Prompt 内容">
48
- <br>
49
- <sub><b>上下文预览</b><br>按类别展开查看实际注入的上下文内容</sub>
50
- </td>
51
- </tr>
52
- </table>
53
-
54
- ## 功能
55
-
56
- ### Claude Code 风格界面
57
-
58
- `extensions/claude-code-style.ts` 提供:
59
-
60
- - Claude Code 风格的工具调用行与结果摘要
61
- - Powerline 状态栏和工作状态提示
62
- - Edit / Write 的 Diff 预览
63
- - 工具结果的折叠与展开
64
- - `/ccstyle` 配置命令和 `Ctrl+Shift+O` 快捷键
65
-
66
- 常用命令:
67
-
68
- ```text
69
- /ccstyle # 切换开关
70
- /ccstyle status # 查看状态
71
- /ccstyle compact # 紧凑摘要
72
- /ccstyle minimal # 最小化输出
73
- /ccstyle powerline # 查看 Powerline 预设
74
- ```
75
-
76
- ### 上下文窗口查看
77
-
78
- `extensions/context.ts` 注册 `/context`,展示当前上下文窗口的使用分布,并可进一步预览:
79
-
80
- - System prompt
81
- - Tools
82
- - Context files
83
- - Skills
84
- - User / assistant messages
85
- - Tool results
86
- - Compaction summaries
87
-
88
- ### 历史 Session 引用
89
-
90
- `extensions/session-reference/index.ts` 将历史 Session 接入 `@` 补全:
91
-
92
- 1. 在提示词中输入 `@`。
93
- 2. 从 `[Session] ...` 或 `[SubAgent] ...` 补全项中选择要引用的上下文。
94
- 3. 提交时以 `@session:<session-id>` 引用其当前有效上下文。
95
-
96
- Session 模糊查询默认显示 5 个候选;若已加载 `pi-subagents`,也可直接引用现有 SubAgent。一次提示词可以引用多个 Session;扩展会自动去重,并限制注入规模以避免上下文无限膨胀。更多细节见 [`extensions/session-reference/README.md`](./extensions/session-reference/README.md)。
97
-
98
- ## 安装
99
-
100
- 从 Git 安装指定版本:
101
-
102
- ```bash
103
- pi install git:github.com/minuque/pi-cc-extensions@v0.1.0
104
- ```
105
-
106
- 安装后启动 Pi,即可使用上述扩展。更新到主分支版本:
107
-
108
- ```bash
109
- pi install git:github.com/minuque/pi-cc-extensions
110
- ```
111
-
112
- 安装后可以先运行:
113
-
114
- ```text
115
- /context
116
- /ccstyle status
117
- ```
118
-
119
- ## 本地开发
120
-
121
- ```bash
122
- npm test
123
- pi -e .
124
- ```
125
-
126
- 也可以把当前仓库作为本地 Pi 包安装:
127
-
128
- ```bash
129
- pi install /absolute/path/to/pi-cc-extensions
130
- ```
131
-
132
- 修改扩展后,在 Pi 中执行:
133
-
134
- ```text
135
- /reload
136
- ```
137
-
138
- ## 兼容性
139
-
140
- - Node.js `>=22.19.0`
141
- - 作为 Pi package 加载,入口由根目录 `package.json` 的 `pi.extensions` 显式声明
142
-
143
- ## 发布
144
-
145
- 发布前检查:
146
-
147
- ```bash
148
- npm test
149
- npm pack --dry-run
150
- ```
151
-
152
- 使用 npm 发布。未指定 tag 时使用 `latest`:
153
-
154
- ```bash
155
- npm run release
156
- npm run release -- --tag next
157
- npm run release -- --tag beta
158
- ```
159
-
160
- ## 致谢
161
-
162
- 本项目中的 Powerline 状态栏、Welcome 界面、Working Vibes 等功能基于 [nicobailon/pi-powerline-footer](https://github.com/nicobailon/pi-powerline-footer) 二次开发,感谢原作者的开源贡献。
1
+ <p align="center">
2
+ <img src="./assets/readme/hero.svg" width="100%" alt="pi-cc-extensions:为 Pi 提供 Claude Code 风格界面、上下文洞察和历史 Session 引用">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="#安装"><img alt="Pi package" src="https://img.shields.io/badge/Pi-package-58B7FF?style=flat-square"></a>
7
+ <a href="./package.json"><img alt="Version 0.1.3" src="https://img.shields.io/badge/version-0.1.3-66E3C4?style=flat-square"></a>
8
+ <a href="#兼容性"><img alt="Node.js 22.19 or newer" src="https://img.shields.io/badge/Node.js-%E2%89%A522.19-66E3C4?style=flat-square"></a>
9
+ <a href="./extensions"><img alt="TypeScript extensions" src="https://img.shields.io/badge/TypeScript-extensions-3178C6?style=flat-square"></a>
10
+ </p>
11
+
12
+ <p align="center">
13
+ 为 Pi 补充一套更接近 Claude Code 的终端工作流 · 上下文洞察 · 历史 Session 引用
14
+ </p>
15
+
16
+ ---
17
+
18
+ ## 截图
19
+
20
+ <table>
21
+ <tr>
22
+ <td colspan="2" align="center">
23
+ <img src="./assets/readme/welcome.webp" width="100%" alt="Pi 的 Welcome 界面,展示 Tips、已加载上下文、扩展和最近会话">
24
+ <br>
25
+ <sub><b>Welcome 界面</b><br>启动时查看已加载上下文、扩展与最近会话</sub>
26
+ </td>
27
+ </tr>
28
+ <tr>
29
+ <td width="50%" align="center">
30
+ <img src="./assets/readme/showcase.webp" width="100%" alt="Pi 终端中的 Claude Code 风格扩展界面,包含工具调用摘要、Diff 预览和 Powerline 状态栏">
31
+ <br>
32
+ <sub><b>Claude Code 风格界面</b><br>工具调用摘要、Diff 预览与 Powerline 状态栏</sub>
33
+ </td>
34
+ <td width="50%" align="center">
35
+ <img src="./assets/readme/session-reference.webp" width="100%" alt="在 Pi 中通过 @ 补全选择历史 Session">
36
+ <br>
37
+ <sub><b>历史 Session 引用</b><br>通过 @ 补全快速找到并引用历史会话</sub>
38
+ </td>
39
+ </tr>
40
+ <tr>
41
+ <td width="50%" align="center">
42
+ <img src="./assets/readme/context-usage.webp" width="100%" alt="Pi 上下文窗口用量查看器">
43
+ <br>
44
+ <sub><b>上下文用量</b><br>查看 System prompt、Tools、Skills 等占用分布</sub>
45
+ </td>
46
+ <td width="50%" align="center">
47
+ <img src="./assets/readme/context-preview.webp" width="100%" alt="Pi 中展开查看 System Prompt 内容">
48
+ <br>
49
+ <sub><b>上下文预览</b><br>按类别展开查看实际注入的上下文内容</sub>
50
+ </td>
51
+ </tr>
52
+ </table>
53
+
54
+ ## 功能
55
+
56
+ ### Claude Code 风格界面
57
+
58
+ `extensions/claude-code-style.ts` 提供:
59
+
60
+ - Claude Code 风格的工具调用行与结果摘要
61
+ - Powerline 状态栏和工作状态提示
62
+ - Edit / Write 的 Diff 预览
63
+ - 工具结果的折叠与展开
64
+ - `/ccstyle` 配置命令和 `Ctrl+Shift+O` 快捷键
65
+
66
+ 常用命令:
67
+
68
+ ```text
69
+ /ccstyle # 切换开关
70
+ /ccstyle status # 查看状态
71
+ /ccstyle compact # 紧凑摘要
72
+ /ccstyle minimal # 最小化输出
73
+ /ccstyle powerline # 查看 Powerline 预设
74
+ ```
75
+
76
+ ### 上下文窗口查看
77
+
78
+ `extensions/context.ts` 注册 `/context`,展示当前上下文窗口的使用分布,并可进一步预览:
79
+
80
+ - System prompt
81
+ - Tools
82
+ - Context files
83
+ - Skills
84
+ - User / assistant messages
85
+ - Tool results
86
+ - Compaction summaries
87
+
88
+ ### 历史 Session 引用
89
+
90
+ `extensions/session-reference/index.ts` 将历史 Session 接入 `@` 补全:
91
+
92
+ 1. 在提示词中输入 `@`。
93
+ 2. 从 `[Session] ...` 或 `[SubAgent] ...` 补全项中选择要引用的上下文。
94
+ 3. 提交时以 `@session:<session-id>` 引用其当前有效上下文。
95
+
96
+ Session 模糊查询默认显示 5 个候选;若已加载 `pi-subagents`,也可直接引用现有 SubAgent。一次提示词可以引用多个 Session;扩展会自动去重,并限制注入规模以避免上下文无限膨胀。更多细节见 [`extensions/session-reference/README.md`](./extensions/session-reference/README.md)。
97
+
98
+ ## 安装
99
+
100
+ 从 npm 安装(推荐):
101
+
102
+ ```bash
103
+ pi install npm:pi-cc-extensions
104
+ ```
105
+
106
+ 或从 Git 安装:
107
+
108
+ ```bash
109
+ pi install git:github.com/minuque/pi-cc-extensions
110
+ ```
111
+
112
+ 安装后可以先运行:
113
+
114
+ ```text
115
+ /context
116
+ /ccstyle status
117
+ ```
118
+
119
+ ## 本地开发
120
+
121
+ ```bash
122
+ npm test
123
+ pi -e .
124
+ ```
125
+
126
+ 也可以把当前仓库作为本地 Pi 包安装:
127
+
128
+ ```bash
129
+ pi install /absolute/path/to/pi-cc-extensions
130
+ ```
131
+
132
+ 修改扩展后,在 Pi 中执行:
133
+
134
+ ```text
135
+ /reload
136
+ ```
137
+
138
+ ## 兼容性
139
+
140
+ - Node.js `>=22.19.0`
141
+ - 作为 Pi package 加载,入口由根目录 `package.json` 的 `pi.extensions` 显式声明
142
+
143
+ ## 发布
144
+
145
+ 发布前检查:
146
+
147
+ ```bash
148
+ npm test
149
+ npm pack --dry-run
150
+ ```
151
+
152
+ 使用 npm 发布。未指定 tag 时使用 `latest`:
153
+
154
+ ```bash
155
+ npm run release
156
+ npm run release -- --tag next
157
+ npm run release -- --tag beta
158
+ ```
159
+
160
+ ## 致谢
161
+
162
+ 本项目中的 Powerline 状态栏、Welcome 界面、Working Vibes 等功能基于 [nicobailon/pi-powerline-footer](https://github.com/nicobailon/pi-powerline-footer) 二次开发,感谢原作者的开源贡献。
@@ -0,0 +1,175 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
+ import {
6
+ type AutocompleteItem,
7
+ type AutocompleteProvider,
8
+ type AutocompleteSuggestions,
9
+ fuzzyFilter,
10
+ } from "@earendil-works/pi-tui";
11
+
12
+ type AgentInfo = {
13
+ name: string;
14
+ displayName: string;
15
+ description: string;
16
+ model?: string;
17
+ filePath: string;
18
+ };
19
+
20
+ const MAX_SUGGESTIONS = 10;
21
+ // Match `@name` but NOT `@session:` (reserved by session-reference extension).
22
+ const AGENT_NAME_PATTERN = /(?:^|[\t ])@([^\s:@][^\s:]*)$/;
23
+
24
+ function getAgentDir(): string {
25
+ const override = process.env.PI_CODING_AGENT_DIR;
26
+ return join(override ? override : homedir(), ".pi", "agent", "agents");
27
+ }
28
+
29
+ function parseFrontmatter(content: string): Record<string, string> {
30
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
31
+ if (!match) return {};
32
+ const result: Record<string, string> = {};
33
+ for (const line of match[1]!.split("\n")) {
34
+ const kv = line.match(/^(\w+):\s*(.*)$/);
35
+ if (kv) result[kv[1]!] = kv[2]!.trim();
36
+ }
37
+ return result;
38
+ }
39
+
40
+ function loadAgents(): AgentInfo[] {
41
+ const dir = getAgentDir();
42
+ if (!existsSync(dir)) return [];
43
+
44
+ return readdirSync(dir)
45
+ .filter((f) => f.endsWith(".md"))
46
+ .map((file) => {
47
+ const filePath = join(dir, file);
48
+ const content = readFileSync(filePath, "utf-8");
49
+ const fm = parseFrontmatter(content);
50
+ const name = file.replace(/\.md$/, "");
51
+ return {
52
+ name,
53
+ displayName: fm.display_name || name,
54
+ description: fm.description || "",
55
+ model: fm.model,
56
+ filePath,
57
+ };
58
+ });
59
+ }
60
+
61
+ function createAgentAutocompleteProvider(
62
+ current: AutocompleteProvider,
63
+ getAgents: () => AgentInfo[],
64
+ ): AutocompleteProvider {
65
+ return {
66
+ async getSuggestions(
67
+ lines: string[],
68
+ cursorLine: number,
69
+ cursorCol: number,
70
+ options: { signal: AbortSignal; force?: boolean },
71
+ ): Promise<AutocompleteSuggestions | null> {
72
+ const currentLine = lines[cursorLine] ?? "";
73
+ const textBeforeCursor = currentLine.slice(0, cursorCol);
74
+ const match = textBeforeCursor.match(AGENT_NAME_PATTERN);
75
+
76
+ if (!match) {
77
+ return current.getSuggestions(lines, cursorLine, cursorCol, options);
78
+ }
79
+
80
+ const query = match[1]!;
81
+ const agents = getAgents();
82
+
83
+ if (options.signal.aborted || agents.length === 0) {
84
+ return current.getSuggestions(lines, cursorLine, cursorCol, options);
85
+ }
86
+
87
+ const matches = query.trim()
88
+ ? fuzzyFilter(agents, query, (a) => `${a.name} ${a.displayName} ${a.description}`)
89
+ : agents;
90
+
91
+ const items: AutocompleteItem[] = matches
92
+ .slice(0, MAX_SUGGESTIONS)
93
+ .map((agent) => ({
94
+ value: `@${agent.name}`,
95
+ label: `[SubAgent] ${agent.displayName}`,
96
+ description: agent.description
97
+ + (agent.model ? ` · ${agent.model}` : ""),
98
+ }));
99
+
100
+ if (items.length === 0) {
101
+ return current.getSuggestions(lines, cursorLine, cursorCol, options);
102
+ }
103
+
104
+ return { items, prefix: `@${query}` };
105
+ },
106
+
107
+ applyCompletion(
108
+ lines: string[],
109
+ cursorLine: number,
110
+ cursorCol: number,
111
+ item: AutocompleteItem,
112
+ prefix: string,
113
+ ) {
114
+ return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
115
+ },
116
+
117
+ shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number) {
118
+ return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
119
+ },
120
+ };
121
+ }
122
+
123
+ export default function agentAutocompleteExtension(pi: ExtensionAPI): void {
124
+ let cachedAgents: AgentInfo[] | undefined;
125
+
126
+ const getAgents = (): AgentInfo[] => {
127
+ if (!cachedAgents) {
128
+ cachedAgents = loadAgents();
129
+ }
130
+ return cachedAgents;
131
+ };
132
+
133
+ // Reload agents on /reload
134
+ pi.on("resources_discover", () => {
135
+ cachedAgents = undefined;
136
+ });
137
+
138
+ pi.on("session_start", (_event, ctx) => {
139
+ const agents = getAgents();
140
+ if (agents.length === 0 || ctx.mode !== "tui") return;
141
+ ctx.ui.addAutocompleteProvider((current) =>
142
+ createAgentAutocompleteProvider(current, getAgents),
143
+ );
144
+ });
145
+
146
+ // Inject instruction when user types @agent-name (not @session:) in prompt.
147
+ // Supports multiple different subagents in a single prompt.
148
+ const AGENT_PROMPT_PATTERN = /(?:^|[\s])@([^\s:@][^\s:]*)/g;
149
+ pi.on("before_agent_start", async (event, _ctx) => {
150
+ const agents = getAgents();
151
+ if (agents.length === 0) return;
152
+
153
+ const mentions: string[] = [];
154
+ for (const m of event.prompt.matchAll(AGENT_PROMPT_PATTERN)) {
155
+ const name = m[1]!;
156
+ if (agents.some((a) => a.name === name) && !mentions.includes(name)) {
157
+ mentions.push(name);
158
+ }
159
+ }
160
+ if (mentions.length === 0) return;
161
+
162
+ const agentMap = new Map(agents.map((a) => [a.name, a]));
163
+ const agentList = mentions.map((n) => `"${n}" (${agentMap.get(n)!.displayName})`).join(", ");
164
+ const agentTypes = mentions.map((n) => `"${n}"`).join(" | ");
165
+
166
+ return {
167
+ systemPrompt:
168
+ event.systemPrompt
169
+ + `\n\nThe user's prompt references these subagent types: ${agentList}. `
170
+ + `You MUST use the Agent tool for EACH mentioned subagent to delegate the relevant parts of the request. `
171
+ + `Handle different subagents separately — do NOT merge their tasks into a single Agent call. `
172
+ + `For example, if the user mentions @coder and @explore, make two separate Agent tool calls, one with subagent_type="coder" and another with subagent_type="explore".`,
173
+ };
174
+ });
175
+ }