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 +9 -0
- package/dist/i18n.js +9 -1
- package/dist/index.js +2 -3
- package/dist/tui/confirm-apply.js +1 -5
- package/dist/tui/preset-select.js +5 -5
- package/dist/tui/provider-setup.js +3 -84
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/pi-package/extensions/auto-update.ts +71 -0
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
|
-
|
|
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
|
-
|
|
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,
|
|
100
|
-
configs.push({ name, apiKey, defaultModel
|
|
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
|
-
|
|
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
|
@@ -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
|
+
}
|