oh-pi 0.1.65 → 0.1.66

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.
@@ -81,17 +81,22 @@ function detectProviders(agentDir) {
81
81
  */
82
82
  export async function detectEnv() {
83
83
  const agentDir = join(homedir(), ".pi", "agent");
84
- let piVersion = null;
85
- let piInstalled = false;
86
- try {
87
- piVersion = execSync("pi --version", { encoding: "utf8", timeout: 5000 }).trim();
88
- piInstalled = true;
89
- }
90
- catch { /* not installed */ }
91
- const existingFiles = scanDir(agentDir);
84
+ // 并行检测 pi 版本和扫描配置
85
+ const [versionResult, existingFiles] = await Promise.all([
86
+ new Promise((resolve) => {
87
+ try {
88
+ const v = execSync("pi --version", { encoding: "utf8", timeout: 3000 }).trim();
89
+ resolve({ installed: true, version: v });
90
+ }
91
+ catch {
92
+ resolve({ installed: false, version: null });
93
+ }
94
+ }),
95
+ Promise.resolve(scanDir(agentDir)),
96
+ ]);
92
97
  return {
93
- piInstalled,
94
- piVersion,
98
+ piInstalled: versionResult.installed,
99
+ piVersion: versionResult.version,
95
100
  hasExistingConfig: existsSync(join(agentDir, "settings.json")),
96
101
  agentDir,
97
102
  terminal: process.env.TERM_PROGRAM ?? process.env.TERM ?? "unknown",
@@ -12,13 +12,40 @@ function ensureDir(dir) {
12
12
  mkdirSync(dir, { recursive: true });
13
13
  }
14
14
  /**
15
- * 清空并重建目录,若目录已存在则先删除再重新创建
16
- * @param dir - 目标目录路径
15
+ * 增量同步目录:只复制有变化的文件,删除源中不存在的文件
16
+ * @param src - 源目录路径
17
+ * @param dest - 目标目录路径
17
18
  */
18
- function cleanDir(dir) {
19
- if (existsSync(dir))
20
- rmSync(dir, { recursive: true });
21
- ensureDir(dir);
19
+ function syncDir(src, dest) {
20
+ ensureDir(dest);
21
+ const srcEntries = new Set();
22
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
23
+ srcEntries.add(entry.name);
24
+ const srcPath = join(src, entry.name);
25
+ const destPath = join(dest, entry.name);
26
+ if (entry.isDirectory()) {
27
+ syncDir(srcPath, destPath);
28
+ }
29
+ else {
30
+ // 只在文件大小不同时复制
31
+ try {
32
+ if (existsSync(destPath) && statSync(destPath).size === statSync(srcPath).size)
33
+ continue;
34
+ }
35
+ catch { /* copy anyway */ }
36
+ copyFileSync(srcPath, destPath);
37
+ }
38
+ }
39
+ // 删除目标中源不存在的文件
40
+ try {
41
+ for (const entry of readdirSync(dest, { withFileTypes: true })) {
42
+ if (!srcEntries.has(entry.name)) {
43
+ const p = join(dest, entry.name);
44
+ rmSync(p, { recursive: true });
45
+ }
46
+ }
47
+ }
48
+ catch { /* skip */ }
22
49
  }
23
50
  /**
24
51
  * 递归复制目录及其所有内容到目标路径
@@ -149,12 +176,12 @@ export function applyConfig(config) {
149
176
  catch { /* template not found, skip */ }
150
177
  // 6. Copy extensions (single file .ts or directory with index.ts)
151
178
  const extDir = join(agentDir, "extensions");
152
- cleanDir(extDir);
179
+ ensureDir(extDir);
153
180
  for (const ext of config.extensions) {
154
181
  const dirSrc = resources.extension(ext);
155
182
  const fileSrc = resources.extensionFile(ext);
156
183
  if (existsSync(dirSrc) && statSync(dirSrc).isDirectory()) {
157
- copyDir(dirSrc, join(extDir, ext));
184
+ syncDir(dirSrc, join(extDir, ext));
158
185
  }
159
186
  else {
160
187
  try {
@@ -165,7 +192,7 @@ export function applyConfig(config) {
165
192
  }
166
193
  // 7. Copy prompts
167
194
  const promptDir = join(agentDir, "prompts");
168
- cleanDir(promptDir);
195
+ ensureDir(promptDir);
169
196
  for (const p of config.prompts) {
170
197
  const src = resources.prompt(p);
171
198
  try {
@@ -175,19 +202,15 @@ export function applyConfig(config) {
175
202
  }
176
203
  // 8. Copy skills (auto-discover all from pi-package/skills/)
177
204
  const skillDir = join(agentDir, "skills");
178
- cleanDir(skillDir);
179
205
  const skillsSrcDir = resources.skillsDir();
180
206
  try {
181
- for (const entry of readdirSync(skillsSrcDir, { withFileTypes: true })) {
182
- if (entry.isDirectory() && existsSync(join(skillsSrcDir, entry.name, "SKILL.md"))) {
183
- copyDir(join(skillsSrcDir, entry.name), join(skillDir, entry.name));
184
- }
185
- }
207
+ if (existsSync(skillsSrcDir))
208
+ syncDir(skillsSrcDir, skillDir);
186
209
  }
187
210
  catch { /* skills dir not found, skip */ }
188
211
  // 9. Copy themes (only custom ones)
189
212
  const themeDir = join(agentDir, "themes");
190
- cleanDir(themeDir);
213
+ ensureDir(themeDir);
191
214
  const themeSrc = resources.theme(config.theme);
192
215
  try {
193
216
  copyFileSync(themeSrc, join(themeDir, `${config.theme}.json`));
@@ -199,7 +222,7 @@ export function applyConfig(config) {
199
222
  */
200
223
  export function installPi() {
201
224
  try {
202
- execSync("npm install -g @mariozechner/pi-coding-agent", { stdio: "inherit" });
225
+ execSync("npm install -g @mariozechner/pi-coding-agent", { stdio: "pipe", timeout: 120000 });
203
226
  }
204
227
  catch {
205
228
  throw new Error("Failed to install pi-coding-agent");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-pi",
3
- "version": "0.1.65",
3
+ "version": "0.1.66",
4
4
  "description": "One-click setup for pi-coding-agent. Like oh-my-zsh for pi.",
5
5
  "type": "module",
6
6
  "bin": {