oh-pi 0.1.26 → 0.1.27

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/bin/oh-pi.js CHANGED
@@ -1,3 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import { execSync } from "node:child_process";
3
+ // Windows terminals default to non-UTF-8 codepage (e.g. GBK/CP936),
4
+ // causing garbled emoji and Unicode output. Force UTF-8 before any output.
5
+ if (process.platform === "win32") {
6
+ try {
7
+ execSync("chcp 65001", { stdio: "ignore" });
8
+ }
9
+ catch { }
10
+ }
2
11
  import { run } from "../index.js";
3
12
  run().catch((e) => { console.error(e); process.exit(1); });
package/dist/i18n.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as p from "@clack/prompts";
2
+ import { execSync } from "node:child_process";
2
3
  let current = "en";
3
4
  const messages = {
4
5
  en: {
@@ -429,7 +430,14 @@ export function getLocale() { return current; }
429
430
  * @returns 检测到的语言代码,或 undefined
430
431
  */
431
432
  function detectLocale() {
432
- const lang = (process.env.LANG ?? process.env.LC_ALL ?? process.env.LANGUAGE ?? "").toLowerCase();
433
+ let lang = (process.env.LANG ?? process.env.LC_ALL ?? process.env.LANGUAGE ?? "").toLowerCase();
434
+ // Windows doesn't set LANG/LC_ALL — detect via OS locale
435
+ if (!lang && process.platform === "win32") {
436
+ try {
437
+ lang = execSync("powershell -NoProfile -Command \"(Get-Culture).Name\"", { encoding: "utf8", timeout: 3000 }).trim().toLowerCase();
438
+ }
439
+ catch { /* ignore */ }
440
+ }
433
441
  if (lang.startsWith("zh"))
434
442
  return "zh";
435
443
  if (lang.startsWith("fr"))
package/dist/index.js CHANGED
@@ -39,12 +39,11 @@ export async function run() {
39
39
  */
40
40
  async function quickFlow(env) {
41
41
  const providers = await setupProviders(env);
42
- const theme = await selectTheme();
43
42
  return {
44
43
  providers,
45
- theme,
44
+ theme: "dark",
46
45
  keybindings: "default",
47
- extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header"],
46
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header", "auto-update"],
48
47
  skills: ["quick-setup", "debug-helper", "git-workflow"],
49
48
  prompts: ["review", "fix", "explain", "commit", "test"],
50
49
  agents: "general-developer",
@@ -51,11 +51,7 @@ export async function confirmApply(config, env) {
51
51
  }
52
52
  }
53
53
  else {
54
- const ok = await p.confirm({ message: t("confirm.apply") });
55
- if (p.isCancel(ok) || !ok) {
56
- p.cancel(t("confirm.noChanges"));
57
- return;
58
- }
54
+ // New user skip confirmation, apply directly
59
55
  }
60
56
  // ═══ Install pi if needed ═══
61
57
  if (!env.piInstalled) {
@@ -5,7 +5,7 @@ const PRESETS = {
5
5
  labelKey: "preset.starter", hintKey: "preset.starterHint",
6
6
  config: {
7
7
  theme: "dark", keybindings: "default", thinking: "medium",
8
- extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header"],
8
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header", "auto-update"],
9
9
  skills: ["quick-setup", "debug-helper"],
10
10
  prompts: ["review", "fix", "explain", "commit"],
11
11
  agents: "general-developer",
@@ -15,7 +15,7 @@ const PRESETS = {
15
15
  labelKey: "preset.pro", hintKey: "preset.proHint",
16
16
  config: {
17
17
  theme: "catppuccin-mocha", keybindings: "default", thinking: "high",
18
- extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header"],
18
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header", "auto-update"],
19
19
  skills: ["quick-setup", "debug-helper", "git-workflow"],
20
20
  prompts: ["review", "fix", "explain", "commit", "test", "refactor", "optimize", "document", "pr"],
21
21
  agents: "fullstack-developer",
@@ -25,7 +25,7 @@ const PRESETS = {
25
25
  labelKey: "preset.security", hintKey: "preset.securityHint",
26
26
  config: {
27
27
  theme: "cyberpunk", keybindings: "default", thinking: "high",
28
- extensions: ["safe-guard", "custom-footer", "compact-header"],
28
+ extensions: ["safe-guard", "custom-footer", "compact-header", "auto-update"],
29
29
  skills: ["debug-helper"],
30
30
  prompts: ["review", "security", "fix", "explain"],
31
31
  agents: "security-researcher",
@@ -35,7 +35,7 @@ const PRESETS = {
35
35
  labelKey: "preset.dataai", hintKey: "preset.dataaiHint",
36
36
  config: {
37
37
  theme: "tokyo-night", keybindings: "default", thinking: "medium",
38
- extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header"],
38
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header", "auto-update"],
39
39
  skills: ["quick-setup", "debug-helper"],
40
40
  prompts: ["review", "fix", "explain", "optimize", "document", "test"],
41
41
  agents: "data-ai-engineer",
@@ -52,7 +52,7 @@ const PRESETS = {
52
52
  labelKey: "preset.full", hintKey: "preset.fullHint",
53
53
  config: {
54
54
  theme: "dark", keybindings: "default", thinking: "high",
55
- extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header", "ant-colony"],
55
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "compact-header", "ant-colony", "auto-update"],
56
56
  skills: ["quick-setup", "debug-helper", "git-workflow", "ant-colony"],
57
57
  prompts: ["review", "fix", "explain", "commit", "test", "refactor", "optimize", "security", "document", "pr"],
58
58
  agents: "colony-operator",
@@ -73,31 +73,9 @@ export async function setupProviders(env) {
73
73
  else {
74
74
  apiKey = await promptKey(info.label);
75
75
  }
76
- // Ask for custom base URL (optional)
77
- const wantCustomUrl = await p.confirm({
78
- message: t("provider.customEndpoint", { label: info.label }),
79
- initialValue: false,
80
- });
81
- if (p.isCancel(wantCustomUrl)) {
82
- p.cancel(t("cancelled"));
83
- process.exit(0);
84
- }
85
- let baseUrl;
86
- if (wantCustomUrl) {
87
- const url = await p.text({
88
- message: t("provider.baseUrl", { label: info.label }),
89
- placeholder: t("provider.baseUrlPlaceholder"),
90
- validate: (v) => (!v || !v.startsWith("http")) ? t("provider.baseUrlValidation") : undefined,
91
- });
92
- if (p.isCancel(url)) {
93
- p.cancel(t("cancelled"));
94
- process.exit(0);
95
- }
96
- baseUrl = url;
97
- }
98
76
  // Model selection — try dynamic fetch, fall back to static list
99
- const defaultModel = await selectModel(info.label, info.models, baseUrl, apiKey);
100
- configs.push({ name, apiKey, defaultModel, ...(baseUrl ? { baseUrl } : {}) });
77
+ const defaultModel = await selectModel(info.label, info.models, undefined, apiKey);
78
+ configs.push({ name, apiKey, defaultModel });
101
79
  p.log.success(t("provider.configured", { label: info.label }));
102
80
  }
103
81
  return configs;
@@ -160,66 +138,7 @@ async function setupCustomProvider() {
160
138
  defaultModel = model;
161
139
  }
162
140
  p.log.success(t("provider.customConfigured", { name, url: baseUrl }));
163
- // Model capabilities (optional)
164
- const wantCaps = await p.confirm({
165
- message: t("provider.configureCaps"),
166
- initialValue: false,
167
- });
168
- if (p.isCancel(wantCaps)) {
169
- p.cancel(t("cancelled"));
170
- process.exit(0);
171
- }
172
- let contextWindow;
173
- let maxTokens;
174
- let reasoning;
175
- let multimodal;
176
- if (wantCaps) {
177
- const ctxInput = await p.text({
178
- message: t("provider.contextWindow"),
179
- placeholder: "128000",
180
- initialValue: "128000",
181
- validate: (v) => {
182
- const n = Number(v);
183
- if (isNaN(n) || n < 1024)
184
- return t("provider.contextWindowValidation");
185
- return undefined;
186
- },
187
- });
188
- if (p.isCancel(ctxInput)) {
189
- p.cancel(t("cancelled"));
190
- process.exit(0);
191
- }
192
- contextWindow = Number(ctxInput);
193
- const maxTokInput = await p.text({
194
- message: t("provider.maxTokens"),
195
- placeholder: "8192",
196
- initialValue: "8192",
197
- validate: (v) => {
198
- const n = Number(v);
199
- if (isNaN(n) || n < 256)
200
- return t("provider.maxTokensValidation");
201
- return undefined;
202
- },
203
- });
204
- if (p.isCancel(maxTokInput)) {
205
- p.cancel(t("cancelled"));
206
- process.exit(0);
207
- }
208
- maxTokens = Number(maxTokInput);
209
- const isMultimodal = await p.confirm({ message: t("provider.multimodal"), initialValue: false });
210
- if (p.isCancel(isMultimodal)) {
211
- p.cancel(t("cancelled"));
212
- process.exit(0);
213
- }
214
- multimodal = isMultimodal;
215
- const isReasoning = await p.confirm({ message: t("provider.reasoning"), initialValue: false });
216
- if (p.isCancel(isReasoning)) {
217
- p.cancel(t("cancelled"));
218
- process.exit(0);
219
- }
220
- reasoning = isReasoning;
221
- }
222
- return { name, apiKey, defaultModel, baseUrl, contextWindow, maxTokens, reasoning, multimodal };
141
+ return { name, apiKey, defaultModel, baseUrl };
223
142
  }
224
143
  async function selectModel(label, staticModels, baseUrl, apiKey) {
225
144
  let models = staticModels;
package/dist/types.js CHANGED
@@ -48,6 +48,7 @@ export const EXTENSIONS = [
48
48
  { name: "custom-footer", label: "📊 Custom Footer — Enhanced status bar with tokens, cost, time, git, cwd", default: true },
49
49
  { name: "compact-header", label: "⚡ Compact Header — Dense startup info replacing verbose output", default: true },
50
50
  { name: "ant-colony", label: "🐜 Ant Colony — Autonomous multi-agent swarm with adaptive concurrency", default: false },
51
+ { name: "auto-update", label: "🔄 Auto Update — Check for oh-pi updates on startup and notify", default: true },
51
52
  ];
52
53
  /** 快捷键绑定方案(default / vim / emacs) */
53
54
  export const KEYBINDING_SCHEMES = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-pi",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "One-click setup for pi-coding-agent. Like oh-my-zsh for pi.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,71 @@
1
+ /**
2
+ * oh-pi Auto Update — check for new oh-pi version on session start
3
+ */
4
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
+ import { execSync } from "node:child_process";
6
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { homedir } from "node:os";
9
+
10
+ const CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24h
11
+ const STAMP_FILE = join(homedir(), ".pi", "agent", ".update-check");
12
+
13
+ function readStamp(): number {
14
+ try { return Number(readFileSync(STAMP_FILE, "utf8").trim()) || 0; } catch { return 0; }
15
+ }
16
+
17
+ function writeStamp() {
18
+ try { writeFileSync(STAMP_FILE, String(Date.now())); } catch {}
19
+ }
20
+
21
+ function getLatestVersion(): string | null {
22
+ try {
23
+ return execSync("npm view oh-pi version", { encoding: "utf8", timeout: 8000 }).trim();
24
+ } catch { return null; }
25
+ }
26
+
27
+ function getCurrentVersion(): string | null {
28
+ // Read from the installed package.json
29
+ try {
30
+ const pkgPath = join(__dirname, "..", "..", "package.json");
31
+ if (existsSync(pkgPath)) {
32
+ return JSON.parse(readFileSync(pkgPath, "utf8")).version;
33
+ }
34
+ } catch {}
35
+ // Fallback: npm list
36
+ try {
37
+ const out = JSON.parse(execSync("npm list -g oh-pi --json --depth=0", { encoding: "utf8", timeout: 8000 }));
38
+ return out.dependencies?.["oh-pi"]?.version ?? null;
39
+ } catch { return null; }
40
+ }
41
+
42
+ function isNewer(latest: string, current: string): boolean {
43
+ const a = latest.split(".").map(Number);
44
+ const b = current.split(".").map(Number);
45
+ for (let i = 0; i < 3; i++) {
46
+ if ((a[i] ?? 0) > (b[i] ?? 0)) return true;
47
+ if ((a[i] ?? 0) < (b[i] ?? 0)) return false;
48
+ }
49
+ return false;
50
+ }
51
+
52
+ export default function (pi: ExtensionAPI) {
53
+ pi.on("session_start", async (_event, ctx) => {
54
+ // Non-blocking: run check in background
55
+ setTimeout(async () => {
56
+ try {
57
+ if (Date.now() - readStamp() < CHECK_INTERVAL) return;
58
+ writeStamp();
59
+
60
+ const current = getCurrentVersion();
61
+ const latest = getLatestVersion();
62
+ if (!current || !latest || !isNewer(latest, current)) return;
63
+
64
+ const msg = `oh-pi ${latest} available (current: ${current}). Run: npx oh-pi@latest`;
65
+ if (ctx.hasUI) {
66
+ ctx.ui.toast?.(msg) ?? console.log(`\n💡 ${msg}\n`);
67
+ }
68
+ } catch {}
69
+ }, 2000);
70
+ });
71
+ }