oh-pi 0.1.81 → 0.1.82

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/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import * as p from "@clack/prompts";
2
1
  import chalk from "chalk";
3
2
  import { selectLanguage, getLocale } from "./i18n.js";
4
3
  import { t } from "./i18n.js";
@@ -10,6 +9,7 @@ import { selectTheme } from "./tui/theme-select.js";
10
9
  import { selectKeybindings } from "./tui/keybinding-select.js";
11
10
  import { selectExtensions } from "./tui/extension-select.js";
12
11
  import { selectAgents } from "./tui/agents-select.js";
12
+ import { runHorizontalTabs } from "./tui/horizontal-tabs.js";
13
13
  import { confirmApply } from "./tui/confirm-apply.js";
14
14
  import { detectEnv } from "./utils/detect.js";
15
15
  import { EXTENSIONS } from "./registry.js";
@@ -58,8 +58,7 @@ async function quickFlow(env) {
58
58
  */
59
59
  async function presetFlow(env) {
60
60
  const preset = await selectPreset();
61
- const providerSetup = await setupProviders(env);
62
- return { ...preset, ...providerSetup };
61
+ return runTabbedFlow(env, preset);
63
62
  }
64
63
  /**
65
64
  * 自定义配置流程。用户逐项选择主题、快捷键、扩展、代理等,并可配置高级选项(如自动压缩阈值)。
@@ -67,67 +66,95 @@ async function presetFlow(env) {
67
66
  * @returns 生成的配置对象
68
67
  */
69
68
  async function customFlow(env) {
69
+ const defaultExtensions = EXTENSIONS.filter(e => e.default).map(e => e.name);
70
+ return runTabbedFlow(env, {
71
+ theme: "dark",
72
+ keybindings: "default",
73
+ extensions: defaultExtensions,
74
+ prompts: ["review", "fix", "explain", "commit", "test", "refactor", "optimize", "security", "document", "pr"],
75
+ agents: "general-developer",
76
+ thinking: "medium",
77
+ });
78
+ }
79
+ async function runTabbedFlow(env, initial) {
70
80
  const defaultExtensions = EXTENSIONS.filter(e => e.default).map(e => e.name);
71
81
  let providerSetup = null;
72
- let theme = "dark";
73
- let keybindings = "default";
74
- let extensions = defaultExtensions;
75
- let agents = "general-developer";
76
- while (true) {
77
- const tabBar = [
78
- chalk.cyan(`[${t("custom.tabProviders")}]`),
79
- chalk.cyan(`[${t("custom.tabAppearance")}]`),
80
- chalk.cyan(`[${t("custom.tabFeatures")}]`),
81
- chalk.cyan(`[${t("custom.tabAgents")}]`),
82
- chalk.green(`[${t("custom.tabFinish")}]`),
83
- ].join(chalk.gray(" | "));
84
- const providerStatus = summarizeProviders(providerSetup);
85
- p.note(`${tabBar}\n${providerStatus}`, t("custom.tabHeader"));
86
- const tab = await p.select({
87
- message: t("custom.tabPrompt"),
88
- options: [
89
- { value: "providers", label: t("custom.tabProviders"), hint: providerStatus },
90
- { value: "appearance", label: t("custom.tabAppearance"), hint: `${theme} · ${keybindings}` },
91
- { value: "features", label: t("custom.tabFeatures"), hint: t("custom.tabFeaturesHint", { count: extensions.length }) },
92
- { value: "agents", label: t("custom.tabAgents"), hint: agents },
93
- { value: "finish", label: t("custom.tabFinish"), hint: t("custom.tabFinishHint") },
94
- ],
95
- });
96
- if (p.isCancel(tab)) {
97
- p.cancel(t("cancelled"));
98
- process.exit(0);
99
- }
100
- if (tab === "providers") {
101
- providerSetup = await setupProviders(env);
102
- continue;
103
- }
104
- if (tab === "appearance") {
105
- theme = await selectTheme();
106
- keybindings = await selectKeybindings();
107
- continue;
108
- }
109
- if (tab === "features") {
110
- extensions = await selectExtensions();
111
- continue;
112
- }
113
- if (tab === "agents") {
114
- agents = await selectAgents();
115
- continue;
116
- }
117
- if (!providerSetup) {
118
- p.log.warn(t("custom.needProviders"));
119
- continue;
120
- }
121
- break;
82
+ let theme = initial.theme;
83
+ let keybindings = initial.keybindings;
84
+ let extensions = initial.extensions.length > 0 ? [...initial.extensions] : defaultExtensions;
85
+ let agents = initial.agents;
86
+ await runHorizontalTabs({
87
+ title: t("custom.tabHeader"),
88
+ canFinish: () => !!providerSetup,
89
+ finishBlockedMessage: () => t("custom.needProviders"),
90
+ tabs: [
91
+ {
92
+ label: t("custom.tabProviders"),
93
+ summary: () => summarizeProviders(providerSetup),
94
+ details: () => providerDetails(providerSetup),
95
+ edit: async () => {
96
+ providerSetup = await setupProviders(env);
97
+ },
98
+ },
99
+ {
100
+ label: t("custom.tabAppearance"),
101
+ summary: () => `${t("confirm.theme")} ${theme} · ${t("confirm.keybindings")} ${keybindings}`,
102
+ details: () => [
103
+ `${chalk.dim(t("confirm.theme"))} ${chalk.cyan(theme)}`,
104
+ `${chalk.dim(t("confirm.keybindings"))} ${chalk.cyan(keybindings)}`,
105
+ ],
106
+ edit: async () => {
107
+ theme = await selectTheme();
108
+ keybindings = await selectKeybindings();
109
+ },
110
+ },
111
+ {
112
+ label: t("custom.tabFeatures"),
113
+ summary: () => t("custom.tabFeaturesHint", { count: extensions.length }),
114
+ details: () => [
115
+ chalk.dim(t("confirm.extensions")),
116
+ extensions.length > 0 ? ` ${extensions.join(", ")}` : ` ${t("confirm.none")}`,
117
+ ],
118
+ edit: async () => {
119
+ extensions = await selectExtensions();
120
+ },
121
+ },
122
+ {
123
+ label: t("custom.tabAgents"),
124
+ summary: () => `${t("confirm.agents")} ${agents}`,
125
+ details: () => [
126
+ `${chalk.dim(t("confirm.agents"))} ${chalk.cyan(agents)}`,
127
+ `${chalk.dim(t("confirm.thinking"))} ${chalk.cyan(initial.thinking)}`,
128
+ ],
129
+ edit: async () => {
130
+ agents = await selectAgents();
131
+ },
132
+ },
133
+ {
134
+ label: t("custom.tabFinish"),
135
+ summary: () => t("custom.tabFinishHint"),
136
+ details: () => [
137
+ chalk.dim(t("custom.tabFinishHelp")),
138
+ ],
139
+ edit: async () => {
140
+ // Finish tab is read-only; use key F to complete.
141
+ },
142
+ },
143
+ ],
144
+ });
145
+ if (!providerSetup) {
146
+ throw new Error("Provider setup is required before finishing tabbed flow");
122
147
  }
148
+ const finalProviderSetup = providerSetup;
123
149
  return {
124
- ...providerSetup,
150
+ providers: finalProviderSetup.providers,
151
+ providerStrategy: finalProviderSetup.providerStrategy,
125
152
  theme,
126
153
  keybindings,
127
154
  extensions,
128
- prompts: ["review", "fix", "explain", "commit", "test", "refactor", "optimize", "security", "document", "pr"],
155
+ prompts: initial.prompts,
129
156
  agents,
130
- thinking: "medium",
157
+ thinking: initial.thinking,
131
158
  };
132
159
  }
133
160
  function summarizeProviders(setup) {
@@ -144,3 +171,17 @@ function summarizeProviders(setup) {
144
171
  return t("confirm.providerStrategyReplace");
145
172
  return t("custom.providersReplace", { list: setup.providers.map(p => p.name).join(", ") });
146
173
  }
174
+ function providerDetails(setup) {
175
+ if (!setup)
176
+ return [chalk.dim(t("custom.needProviders"))];
177
+ if (setup.providerStrategy === "keep")
178
+ return [chalk.dim(t("confirm.providerStrategyKeep"))];
179
+ if (setup.providers.length === 0)
180
+ return [chalk.dim(t("confirm.none"))];
181
+ const primary = setup.providers[0];
182
+ return [
183
+ `${chalk.dim(t("confirm.providerStrategy"))} ${chalk.cyan(setup.providerStrategy)}`,
184
+ `${chalk.dim(t("confirm.providers"))} ${chalk.cyan(setup.providers.map(p => p.name).join(", "))}`,
185
+ `${chalk.dim(t("confirm.model"))} ${chalk.cyan(primary?.defaultModel ?? t("confirm.none"))}`,
186
+ ];
187
+ }
package/dist/locales.js CHANGED
@@ -27,6 +27,8 @@ export const messages = {
27
27
  "custom.tabAgents": "Agent Profile",
28
28
  "custom.tabFinish": "Finish",
29
29
  "custom.tabFinishHint": "Review in next step and apply",
30
+ "custom.tabFinishHelp": "Press F to finish. Press Enter on other tabs to edit.",
31
+ "custom.tabControls": "Controls: ←/→ switch tabs · Enter edit tab · 1-5 jump · F finish · Ctrl+C cancel",
30
32
  "custom.needProviders": "Please configure Providers first.",
31
33
  "custom.providersUnset": "Providers: not configured",
32
34
  "custom.providersAdd": "Add fallback providers: {list}",
@@ -70,6 +72,8 @@ export const messages = {
70
72
  "provider.apiModeResponsesHint": "Modern OpenAI API surface",
71
73
  "provider.apiModeCompletions": "Chat Completions API",
72
74
  "provider.apiModeCompletionsHint": "Legacy-compatible endpoint",
75
+ "provider.apiModeNext": "Next Step",
76
+ "provider.apiModeIntro": "After selecting a model, choose API mode: Responses or Chat Completions.",
73
77
  "provider.configureCaps": "Configure model capabilities? (context window, multimodal, reasoning)",
74
78
  "provider.contextWindow": "Context window size (tokens):",
75
79
  "provider.contextWindowValidation": "Must be a number ≥ 1024",
@@ -190,6 +194,8 @@ export const messages = {
190
194
  "custom.tabAgents": "Agent 模板",
191
195
  "custom.tabFinish": "完成",
192
196
  "custom.tabFinishHint": "下一步可预览并应用",
197
+ "custom.tabFinishHelp": "按 F 完成;在其他页面按 Enter 进入编辑。",
198
+ "custom.tabControls": "操作:←/→ 切换标签 · Enter 编辑当前标签 · 1-5 快速跳转 · F 完成 · Ctrl+C 取消",
193
199
  "custom.needProviders": "请先配置 Providers。",
194
200
  "custom.providersUnset": "Providers:未配置",
195
201
  "custom.providersAdd": "新增备用 Providers:{list}",
@@ -232,6 +238,8 @@ export const messages = {
232
238
  "provider.apiModeResponsesHint": "OpenAI 新版 API",
233
239
  "provider.apiModeCompletions": "Chat Completions API",
234
240
  "provider.apiModeCompletionsHint": "兼容旧接口",
241
+ "provider.apiModeNext": "下一步",
242
+ "provider.apiModeIntro": "选完模型后,需要再选择 API 模式:Responses 或 Chat Completions。",
235
243
  "provider.configureCaps": "配置模型能力?(上下文窗口、多模态、推理)",
236
244
  "provider.contextWindow": "上下文窗口大小(tokens):",
237
245
  "provider.contextWindowValidation": "必须是 ≥ 1024 的数字",
@@ -344,6 +352,8 @@ export const messages = {
344
352
  "custom.tabAgents": "Profil Agent",
345
353
  "custom.tabFinish": "Terminer",
346
354
  "custom.tabFinishHint": "Prévisualiser puis appliquer à l'étape suivante",
355
+ "custom.tabFinishHelp": "Appuyez sur F pour terminer ; appuyez sur Entrée sur les autres onglets pour éditer.",
356
+ "custom.tabControls": "Contrôles : ←/→ changer d'onglet · Entrée éditer · 1-5 accès rapide · F terminer · Ctrl+C annuler",
347
357
  "custom.needProviders": "Veuillez d'abord configurer les fournisseurs.",
348
358
  "custom.providersUnset": "Fournisseurs : non configurés",
349
359
  "custom.providersAdd": "Ajouter des fournisseurs de secours : {list}",
@@ -386,6 +396,8 @@ export const messages = {
386
396
  "provider.apiModeResponsesHint": "Surface API OpenAI moderne",
387
397
  "provider.apiModeCompletions": "API Chat Completions",
388
398
  "provider.apiModeCompletionsHint": "Endpoint compatible historique",
399
+ "provider.apiModeNext": "Étape suivante",
400
+ "provider.apiModeIntro": "Après le choix du modèle, choisissez le mode API : Responses ou Chat Completions.",
389
401
  "provider.configureCaps": "Configurer les capacités du modèle ? (fenêtre de contexte, multimodal, raisonnement)",
390
402
  "provider.contextWindow": "Taille de la fenêtre de contexte (tokens) :",
391
403
  "provider.contextWindowValidation": "Doit être un nombre ≥ 1024",
@@ -0,0 +1,14 @@
1
+ export interface HorizontalTabItem {
2
+ label: string;
3
+ summary: () => string;
4
+ details?: () => string[];
5
+ edit: () => Promise<void>;
6
+ }
7
+ interface HorizontalTabsOptions {
8
+ title: string;
9
+ tabs: HorizontalTabItem[];
10
+ canFinish: () => boolean;
11
+ finishBlockedMessage?: () => string;
12
+ }
13
+ export declare function runHorizontalTabs(opts: HorizontalTabsOptions): Promise<void>;
14
+ export {};
@@ -0,0 +1,106 @@
1
+ import * as p from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import { emitKeypressEvents } from "node:readline";
4
+ import { t } from "../i18n.js";
5
+ function clearScreen() {
6
+ process.stdout.write("\x1b[2J\x1b[H");
7
+ }
8
+ function waitForAction(tabCount) {
9
+ return new Promise((resolve) => {
10
+ const stdin = process.stdin;
11
+ const isRawCapable = !!stdin.isTTY && typeof stdin.setRawMode === "function";
12
+ emitKeypressEvents(stdin);
13
+ if (isRawCapable)
14
+ stdin.setRawMode(true);
15
+ const done = (action) => {
16
+ stdin.off("keypress", onKeypress);
17
+ if (isRawCapable)
18
+ stdin.setRawMode(false);
19
+ resolve(action);
20
+ };
21
+ const onKeypress = (str, key) => {
22
+ if (key.ctrl && key.name === "c")
23
+ return done("cancel");
24
+ if (key.name === "left")
25
+ return done("left");
26
+ if (key.name === "right")
27
+ return done("right");
28
+ if (key.name === "return" || key.name === "space" || key.name === "e")
29
+ return done("edit");
30
+ if (key.name === "f")
31
+ return done("finish");
32
+ if (/^[1-9]$/.test(str)) {
33
+ const idx = Number(str) - 1;
34
+ if (idx >= 0 && idx < tabCount)
35
+ return done({ jump: idx });
36
+ }
37
+ };
38
+ stdin.on("keypress", onKeypress);
39
+ if (!stdin.isTTY) {
40
+ done("cancel");
41
+ }
42
+ });
43
+ }
44
+ function renderTabs(title, tabs, activeIndex, notice) {
45
+ clearScreen();
46
+ console.log(chalk.bold(title));
47
+ console.log();
48
+ const tabLine = tabs
49
+ .map((tab, i) => {
50
+ const label = `${i + 1}. ${tab.label}`;
51
+ return i === activeIndex
52
+ ? chalk.bgCyan.black(` ${label} `)
53
+ : chalk.gray(` ${label} `);
54
+ })
55
+ .join(chalk.gray(" | "));
56
+ console.log(tabLine);
57
+ console.log(chalk.dim(t("custom.tabControls")));
58
+ console.log();
59
+ const active = tabs[activeIndex];
60
+ console.log(chalk.cyan(active.summary()));
61
+ const details = active.details?.() ?? [];
62
+ for (const line of details)
63
+ console.log(line);
64
+ if (notice) {
65
+ console.log();
66
+ console.log(chalk.yellow(notice));
67
+ }
68
+ }
69
+ export async function runHorizontalTabs(opts) {
70
+ const tabs = opts.tabs;
71
+ let activeIndex = 0;
72
+ let notice;
73
+ while (true) {
74
+ renderTabs(opts.title, tabs, activeIndex, notice);
75
+ notice = undefined;
76
+ const action = await waitForAction(tabs.length);
77
+ if (action === "cancel") {
78
+ p.cancel(t("cancelled"));
79
+ process.exit(0);
80
+ }
81
+ if (action === "left") {
82
+ activeIndex = (activeIndex - 1 + tabs.length) % tabs.length;
83
+ continue;
84
+ }
85
+ if (action === "right") {
86
+ activeIndex = (activeIndex + 1) % tabs.length;
87
+ continue;
88
+ }
89
+ if (action === "finish") {
90
+ if (opts.canFinish()) {
91
+ clearScreen();
92
+ return;
93
+ }
94
+ notice = opts.finishBlockedMessage?.() ?? t("custom.needProviders");
95
+ continue;
96
+ }
97
+ if (action === "edit") {
98
+ await tabs[activeIndex].edit();
99
+ continue;
100
+ }
101
+ if (typeof action === "object" && "jump" in action) {
102
+ activeIndex = action.jump;
103
+ continue;
104
+ }
105
+ }
106
+ }
@@ -310,7 +310,7 @@ async function setupProviderChoice(choice) {
310
310
  const { defaultModel, discoveredModels, api } = await selectModelWithMeta(name, info.label, info.models, fetchUrl, apiKey);
311
311
  let finalApi = api;
312
312
  if (name === "openai") {
313
- finalApi = await selectOpenAIApiMode(info.label, defaultModel);
313
+ finalApi = await selectOpenAIApiModeWithHint(info.label, defaultModel);
314
314
  }
315
315
  p.log.success(t("provider.configured", { label: info.label }));
316
316
  return { name, apiKey, defaultModel, baseUrl, api: finalApi, discoveredModels };
@@ -330,6 +330,10 @@ async function selectOpenAIApiMode(label, defaultModel) {
330
330
  }
331
331
  return resolveOpenAIApiMode(selected, defaultModel);
332
332
  }
333
+ async function selectOpenAIApiModeWithHint(label, defaultModel) {
334
+ p.note(t("provider.apiModeIntro"), t("provider.apiModeNext"));
335
+ return selectOpenAIApiMode(label, defaultModel);
336
+ }
333
337
  /**
334
338
  * Interactively configure a custom provider (Ollama, vLLM, or other OpenAI-compatible endpoints).
335
339
  * @returns Custom provider config, or null if cancelled
@@ -364,7 +368,7 @@ async function setupCustomProvider() {
364
368
  }
365
369
  const { defaultModel, discoveredModels, api } = await selectModelWithMeta(name, name, [], baseUrl, apiKey);
366
370
  const finalApi = isOpenAICompatibleApi(api)
367
- ? await selectOpenAIApiMode(name, defaultModel)
371
+ ? await selectOpenAIApiModeWithHint(name, defaultModel)
368
372
  : api;
369
373
  p.log.success(t("provider.customConfigured", { name, url: baseUrl }));
370
374
  return { name, apiKey, defaultModel, baseUrl, api: finalApi, discoveredModels };
@@ -11,6 +11,10 @@ export declare function syncDir(src: string, dest: string): void;
11
11
  * 应用 OhP 配置,生成并写入 ~/.pi/agent/ 下的所有配置文件
12
12
  */
13
13
  export declare function applyConfig(config: OhPConfig): void;
14
+ /**
15
+ * Remove all files/dirs managed by oh-pi before strict replace apply.
16
+ */
17
+ export declare function cleanupManagedConfig(agentDir: string): void;
14
18
  /**
15
19
  * 全局安装 pi-coding-agent,安装失败时抛出异常
16
20
  */
@@ -3,6 +3,17 @@ import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { execSync } from "node:child_process";
5
5
  import { writeProviderEnv, writeModelConfig, writeKeybindings, writeAgents, writeExtensions, writePrompts, writeSkills, writeTheme } from "./writers.js";
6
+ const MANAGED_CONFIG_ENTRIES = [
7
+ "auth.json",
8
+ "settings.json",
9
+ "models.json",
10
+ "keybindings.json",
11
+ "AGENTS.md",
12
+ "extensions",
13
+ "prompts",
14
+ "skills",
15
+ "themes",
16
+ ];
6
17
  /**
7
18
  * 确保目录存在,若不存在则递归创建
8
19
  */
@@ -62,6 +73,9 @@ function copyDir(src, dest) {
62
73
  export function applyConfig(config) {
63
74
  const agentDir = join(homedir(), ".pi", "agent");
64
75
  ensureDir(agentDir);
76
+ if ((config.providerStrategy ?? "replace") === "replace") {
77
+ cleanupManagedConfig(agentDir);
78
+ }
65
79
  writeProviderEnv(agentDir, config);
66
80
  writeModelConfig(agentDir, config);
67
81
  writeKeybindings(agentDir, config);
@@ -71,6 +85,14 @@ export function applyConfig(config) {
71
85
  writeSkills(agentDir, config);
72
86
  writeTheme(agentDir, config);
73
87
  }
88
+ /**
89
+ * Remove all files/dirs managed by oh-pi before strict replace apply.
90
+ */
91
+ export function cleanupManagedConfig(agentDir) {
92
+ for (const entry of MANAGED_CONFIG_ENTRIES) {
93
+ rmSync(join(agentDir, entry), { recursive: true, force: true });
94
+ }
95
+ }
74
96
  /**
75
97
  * 全局安装 pi-coding-agent,安装失败时抛出异常
76
98
  */
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { existsSync, mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { cleanupManagedConfig } from "./install.js";
6
+ const tempDirs = [];
7
+ function makeTempDir() {
8
+ const dir = mkdtempSync(join(tmpdir(), "oh-pi-install-"));
9
+ tempDirs.push(dir);
10
+ return dir;
11
+ }
12
+ afterEach(() => {
13
+ for (const dir of tempDirs.splice(0)) {
14
+ rmSync(dir, { recursive: true, force: true });
15
+ }
16
+ });
17
+ describe("cleanupManagedConfig", () => {
18
+ it("removes managed files and directories while preserving unmanaged data", () => {
19
+ const dir = makeTempDir();
20
+ writeFileSync(join(dir, "auth.json"), "{}");
21
+ writeFileSync(join(dir, "settings.json"), "{}");
22
+ writeFileSync(join(dir, "models.json"), "{}");
23
+ writeFileSync(join(dir, "keybindings.json"), "{}");
24
+ writeFileSync(join(dir, "AGENTS.md"), "# test");
25
+ mkdirSync(join(dir, "extensions"), { recursive: true });
26
+ writeFileSync(join(dir, "extensions", "x.ts"), "export default {}");
27
+ mkdirSync(join(dir, "prompts"), { recursive: true });
28
+ writeFileSync(join(dir, "prompts", "x.md"), "prompt");
29
+ mkdirSync(join(dir, "skills"), { recursive: true });
30
+ writeFileSync(join(dir, "skills", "x.md"), "skill");
31
+ mkdirSync(join(dir, "themes"), { recursive: true });
32
+ writeFileSync(join(dir, "themes", "x.json"), "{}");
33
+ mkdirSync(join(dir, "sessions"), { recursive: true });
34
+ writeFileSync(join(dir, "sessions", "keep.json"), "{}");
35
+ writeFileSync(join(dir, "pi-crash.log"), "keep");
36
+ cleanupManagedConfig(dir);
37
+ expect(existsSync(join(dir, "auth.json"))).toBe(false);
38
+ expect(existsSync(join(dir, "settings.json"))).toBe(false);
39
+ expect(existsSync(join(dir, "models.json"))).toBe(false);
40
+ expect(existsSync(join(dir, "keybindings.json"))).toBe(false);
41
+ expect(existsSync(join(dir, "AGENTS.md"))).toBe(false);
42
+ expect(existsSync(join(dir, "extensions"))).toBe(false);
43
+ expect(existsSync(join(dir, "prompts"))).toBe(false);
44
+ expect(existsSync(join(dir, "skills"))).toBe(false);
45
+ expect(existsSync(join(dir, "themes"))).toBe(false);
46
+ expect(existsSync(join(dir, "sessions", "keep.json"))).toBe(true);
47
+ expect(existsSync(join(dir, "pi-crash.log"))).toBe(true);
48
+ });
49
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-pi",
3
- "version": "0.1.81",
3
+ "version": "0.1.82",
4
4
  "description": "One-click setup for pi-coding-agent. Like oh-my-zsh for pi.",
5
5
  "type": "module",
6
6
  "bin": {