oh-pi 0.1.7 β†’ 0.1.8

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,5 +1,5 @@
1
1
  import * as p from "@clack/prompts";
2
- import { selectLanguage } from "./i18n.js";
2
+ import { selectLanguage, getLocale } from "./i18n.js";
3
3
  import { t } from "./i18n.js";
4
4
  import { welcome } from "./tui/welcome.js";
5
5
  import { selectMode } from "./tui/mode-select.js";
@@ -26,6 +26,7 @@ export async function run() {
26
26
  else {
27
27
  config = await customFlow(env);
28
28
  }
29
+ config.locale = getLocale();
29
30
  await confirmApply(config, env);
30
31
  }
31
32
  async function quickFlow(env) {
@@ -35,7 +36,7 @@ async function quickFlow(env) {
35
36
  providers,
36
37
  theme,
37
38
  keybindings: "default",
38
- extensions: ["safe-guard", "git-guard", "auto-session-name"],
39
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "startup-banner"],
39
40
  skills: ["quick-setup", "debug-helper", "git-workflow"],
40
41
  prompts: ["review", "fix", "explain", "commit", "test"],
41
42
  agents: "general-developer",
@@ -5,7 +5,7 @@ const PRESETS = {
5
5
  labelKey: "preset.starter", hintKey: "preset.starterHint",
6
6
  config: {
7
7
  theme: "oh-p-dark", keybindings: "default", thinking: "medium",
8
- extensions: ["safe-guard", "git-guard", "auto-session-name"],
8
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "startup-banner"],
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"],
18
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "startup-banner"],
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"],
28
+ extensions: ["safe-guard", "custom-footer", "startup-banner"],
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"],
38
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "startup-banner"],
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: "oh-p-dark", keybindings: "default", thinking: "high",
55
- extensions: ["safe-guard", "git-guard", "auto-session-name", "ant-colony"],
55
+ extensions: ["safe-guard", "git-guard", "auto-session-name", "custom-footer", "startup-banner", "ant-colony"],
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",
package/dist/types.d.ts CHANGED
@@ -17,6 +17,7 @@ export interface OhPConfig {
17
17
  prompts: string[];
18
18
  agents: string;
19
19
  thinking: string;
20
+ locale?: string;
20
21
  compactThreshold?: number;
21
22
  }
22
23
  /** Official model capabilities for known providers */
package/dist/types.js CHANGED
@@ -41,7 +41,7 @@ export const EXTENSIONS = [
41
41
  { name: "safe-guard", label: "πŸ›‘οΈ Safe Guard β€” Dangerous command confirm + path protection", default: true },
42
42
  { name: "git-guard", label: "πŸ“¦ Git Guard β€” Auto stash checkpoint + dirty repo warning + notify", default: true },
43
43
  { name: "auto-session-name", label: "πŸ“ Auto Session Name β€” Name sessions from first message", default: true },
44
- { name: "custom-footer", label: "πŸ“Š Custom Footer β€” Enhanced status bar with tokens, cost, time, git, cwd", default: false },
44
+ { name: "custom-footer", label: "πŸ“Š Custom Footer β€” Enhanced status bar with tokens, cost, time, git, cwd", default: true },
45
45
  { name: "startup-banner", label: "⚑ Startup Banner β€” Clean compact startup info (replaces verbose output)", default: true },
46
46
  { name: "ant-colony", label: "🐜 Ant Colony β€” Autonomous multi-agent swarm with adaptive concurrency", default: false },
47
47
  ];
@@ -25,10 +25,11 @@ function copyDir(src, dest) {
25
25
  export function applyConfig(config) {
26
26
  const agentDir = join(homedir(), ".pi", "agent");
27
27
  ensureDir(agentDir);
28
- // 1. auth.json (skip if no providers configured)
29
- if (config.providers.length > 0) {
28
+ // 1. auth.json (skip providers that have apiKey in models.json via baseUrl)
29
+ const authProviders = config.providers.filter(p => !p.baseUrl && p.apiKey !== "none");
30
+ if (authProviders.length > 0) {
30
31
  const auth = {};
31
- for (const p of config.providers) {
32
+ for (const p of authProviders) {
32
33
  auth[p.name] = { type: "api_key", key: p.apiKey };
33
34
  }
34
35
  const authPath = join(agentDir, "auth.json");
@@ -61,24 +62,39 @@ export function applyConfig(config) {
61
62
  // 3. models.json (custom endpoints / providers)
62
63
  const customProviders = config.providers.filter(p => p.baseUrl);
63
64
  if (customProviders.length > 0) {
64
- const models = {};
65
+ const providers = {};
65
66
  for (const cp of customProviders) {
66
- const caps = cp.defaultModel ? MODEL_CAPABILITIES[cp.defaultModel] : undefined;
67
- models[cp.name] = {
68
- baseUrl: cp.baseUrl,
69
- apiKey: cp.apiKey === "none" ? undefined : cp.apiKey,
70
- api: "openai-completions",
71
- models: cp.defaultModel ? [{
72
- id: cp.defaultModel,
73
- name: cp.defaultModel,
74
- reasoning: cp.reasoning ?? caps?.reasoning ?? false,
75
- input: cp.multimodal ? ["text", "image"] : (caps?.input ?? ["text"]),
76
- contextWindow: cp.contextWindow ?? caps?.contextWindow ?? 128000,
77
- maxTokens: cp.maxTokens ?? caps?.maxTokens ?? 8192,
78
- }] : [],
79
- };
67
+ const isBuiltin = !!PROVIDERS[cp.name];
68
+ if (isBuiltin) {
69
+ // Known provider with custom baseUrl β€” just override endpoint, keep built-in models
70
+ const entry = { baseUrl: cp.baseUrl };
71
+ if (cp.apiKey !== "none")
72
+ entry.apiKey = cp.apiKey;
73
+ providers[cp.name] = entry;
74
+ }
75
+ else {
76
+ // Fully custom provider β€” need api, models, etc.
77
+ const caps = cp.defaultModel ? MODEL_CAPABILITIES[cp.defaultModel] : undefined;
78
+ const entry = {
79
+ baseUrl: cp.baseUrl,
80
+ api: "openai-completions",
81
+ };
82
+ if (cp.apiKey !== "none")
83
+ entry.apiKey = cp.apiKey;
84
+ if (cp.defaultModel) {
85
+ entry.models = [{
86
+ id: cp.defaultModel,
87
+ name: cp.defaultModel,
88
+ reasoning: cp.reasoning ?? caps?.reasoning ?? false,
89
+ input: cp.multimodal ? ["text", "image"] : (caps?.input ?? ["text"]),
90
+ contextWindow: cp.contextWindow ?? caps?.contextWindow ?? 128000,
91
+ maxTokens: cp.maxTokens ?? caps?.maxTokens ?? 8192,
92
+ }];
93
+ }
94
+ providers[cp.name] = entry;
95
+ }
80
96
  }
81
- writeFileSync(join(agentDir, "models.json"), JSON.stringify(models, null, 2));
97
+ writeFileSync(join(agentDir, "models.json"), JSON.stringify({ providers }, null, 2));
82
98
  }
83
99
  // 4. keybindings.json
84
100
  const kb = KEYBINDING_SCHEMES[config.keybindings];
@@ -88,7 +104,12 @@ export function applyConfig(config) {
88
104
  // 5. AGENTS.md
89
105
  const agentsSrc = join(PKG_ROOT, "pi-package", "agents", `${config.agents}.md`);
90
106
  try {
91
- const content = readFileSync(agentsSrc, "utf8");
107
+ let content = readFileSync(agentsSrc, "utf8");
108
+ if (config.locale && config.locale !== "en") {
109
+ const langNames = { zh: "Chinese (δΈ­ζ–‡)", fr: "French (FranΓ§ais)" };
110
+ const lang = langNames[config.locale] ?? config.locale;
111
+ content = `## Language\nAlways respond in ${lang}. Use the user's language for all conversations and explanations. Code, commands, and technical terms can remain in English.\n\n${content}`;
112
+ }
92
113
  writeFileSync(join(agentDir, "AGENTS.md"), content);
93
114
  }
94
115
  catch { /* template not found, skip */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-pi",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "One-click setup for pi-coding-agent. Like oh-my-zsh for pi.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,32 +1,55 @@
1
- import type { ExtensionContext, PiSdk } from "@anthropic/pi-sdk";
1
+ /**
2
+ * oh-pi Startup Banner Extension
3
+ *
4
+ * Replaces the built-in verbose header with a compact one-line info display.
5
+ * Auto-restores the default header after 8 seconds.
6
+ */
7
+ import type { AssistantMessage } from "@mariozechner/pi-ai";
8
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
9
+
10
+ export default function (pi: ExtensionAPI) {
11
+ pi.on("session_start", async (_event, ctx) => {
12
+ if (!ctx.hasUI) return;
2
13
 
3
- export default function activate(pi: PiSdk) {
4
- pi.on("session_start", async (_event, ctx: ExtensionContext) => {
5
14
  const model = ctx.model;
6
15
  const thinking = pi.getThinkingLevel();
7
16
  const usage = ctx.getContextUsage();
8
- const branch = ctx.sessionManager.getBranch();
9
- const session = ctx.sessionManager.getSessionFile() ?? "ephemeral";
10
17
  const cwd = process.cwd().replace(process.env.HOME ?? "", "~");
11
18
 
19
+ // Sum cost from branch
20
+ let totalCost = 0;
21
+ for (const e of ctx.sessionManager.getBranch()) {
22
+ if (e.type === "message" && e.message.role === "assistant") {
23
+ totalCost += (e.message as AssistantMessage).usage.cost.total;
24
+ }
25
+ }
26
+
12
27
  // Git branch
13
28
  let git = "";
14
29
  try {
15
- const { execSync } = await import("node:child_process");
16
- git = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf8", timeout: 2000 }).trim();
30
+ const { stdout } = await pi.exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 2000 });
31
+ git = stdout.trim();
17
32
  } catch { /* not a git repo */ }
18
33
 
19
34
  const modelStr = model ? `${model.provider}/${model.id}` : "no model";
20
35
  const thinkStr = thinking !== "off" ? ` β€’ 🧠 ${thinking}` : "";
21
36
  const ctxStr = usage ? ` β€’ πŸ“Š ${Math.round(usage.percent)}%` : "";
22
37
  const gitStr = git ? ` β€’ 🌿 ${git}` : "";
23
- const costStr = branch ? ` β€’ $${(branch.totalCost ?? 0).toFixed(3)}` : "";
38
+ const costStr = totalCost > 0 ? ` β€’ $${totalCost.toFixed(3)}` : "";
39
+
40
+ const session = ctx.sessionManager.getSessionFile?.() ?? "ephemeral";
41
+ const sessionName = session.split("/").pop();
24
42
 
25
- const line = `⚑ ${modelStr}${thinkStr}${ctxStr}${costStr} β”‚ πŸ“‚ ${cwd}${gitStr} β”‚ πŸ“‹ ${session.split("/").pop()}`;
43
+ const line = `⚑ ${modelStr}${thinkStr}${ctxStr}${costStr} β”‚ πŸ“‚ ${cwd}${gitStr} β”‚ πŸ“‹ ${sessionName}`;
26
44
 
27
- ctx.ui.setWidget("startup-banner", [line], { placement: "belowEditor" });
45
+ ctx.ui.setHeader((_tui, theme) => ({
46
+ render(_width: number): string[] {
47
+ return ["", theme.fg("accent", line), ""];
48
+ },
49
+ invalidate() {},
50
+ }));
28
51
 
29
- // Auto-hide after 8 seconds
30
- setTimeout(() => ctx.ui.setWidget("startup-banner", undefined), 8000);
52
+ // Restore default header after 8 seconds
53
+ setTimeout(() => ctx.ui.setHeader(undefined), 8000);
31
54
  });
32
55
  }