@yandy0725/pi-ask-user 0.1.0 → 0.2.0

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/README.md CHANGED
@@ -97,17 +97,43 @@ Effective order:
97
97
 
98
98
  1. Per-call `displayMode` parameter (if provided)
99
99
  2. `PI_ASK_USER_DISPLAY_MODE` (if set to `"overlay"` or `"inline"`)
100
- 3. Fallback default: `"overlay"`
100
+ 3. Project config file `.pi/ask-user.json` `displayMode` field (if exists)
101
+ 4. User config file `~/.pi/agent/ask-user.json` `displayMode` field (if exists)
102
+ 5. Fallback default: `"overlay"`
101
103
 
102
104
  Unrecognised values are silently ignored and fall back to `"overlay"`.
103
105
 
106
+ ## Personal preferences via config files
107
+
108
+ In addition to environment variables, you can use JSON config files:
109
+
110
+ **Locations:**
111
+ - User-level (global): `~/.pi/agent/ask-user.json`
112
+ - Project-level: `.pi/ask-user.json`
113
+
114
+ Fields in the project-level file override same-named fields in the user-level file. Priority: call param > env var > project config > user config > default.
115
+
116
+ **Example `~/.pi/agent/ask-user.json`:**
117
+
118
+ ```json
119
+ {
120
+ "displayMode": "inline",
121
+ "overlayToggleKey": "alt+h",
122
+ "commentToggleKey": "alt+c"
123
+ }
124
+ ```
125
+
126
+ All fields are optional. Missing files or malformed JSON are silently ignored, equivalent to no configuration.
127
+
104
128
  ### Shortcuts
105
129
 
106
130
  Effective order for both `overlayToggleKey` and `commentToggleKey`:
107
131
 
108
132
  1. Per-call parameter (if provided)
109
133
  2. Matching env var (`PI_ASK_USER_OVERLAY_TOGGLE_KEY` / `PI_ASK_USER_COMMENT_TOGGLE_KEY`)
110
- 3. Built-in defaults: `alt+o` and `ctrl+g`
134
+ 3. Project config file `.pi/ask-user.json` (if exists)
135
+ 4. User config file `~/.pi/agent/ask-user.json` (if exists)
136
+ 5. Built-in defaults: `alt+o` and `ctrl+g`
111
137
 
112
138
  Pass `"off"`, `"none"`, or `"disabled"` (at any level) to disable the shortcut entirely. Invalid specs are silently dropped and the next source is used. Specs follow the Pi-TUI [`KeyId`](https://github.com/earendil-works/pi-mono/blob/main/packages/tui/src/keys.ts) format: `[mod+]...key` where modifiers are `ctrl`, `shift`, `alt`, `super`, in any order, joined by `+` (e.g. `ctrl+g`, `alt+shift+x`, `escape`, `tab`).
113
139
 
package/README.zh.md CHANGED
@@ -80,17 +80,43 @@ export PI_ASK_USER_COMMENT_TOGGLE_KEY=alt+c
80
80
 
81
81
  1. 每次调用时的 `displayMode` 参数(如提供)
82
82
  2. `PI_ASK_USER_DISPLAY_MODE`(如设置为 `"overlay"` 或 `"inline"`)
83
- 3. 回退默认值:`"overlay"`
83
+ 3. 项目配置文件 `.pi/ask-user.json`(如存在)中的 `displayMode`
84
+ 4. 用户配置文件 `~/.pi/agent/ask-user.json`(如存在)中的 `displayMode`
85
+ 5. 回退默认值:`"overlay"`
84
86
 
85
87
  无法识别的值会被静默忽略并回退到 `"overlay"`。
86
88
 
89
+ ## 通过配置文件设置个人偏好
90
+
91
+ 除了环境变量,也可以通过 JSON 配置文件设置:
92
+
93
+ **文件位置:**
94
+ - 用户级(全局):`~/.pi/agent/ask-user.json`
95
+ - 项目级:`.pi/ask-user.json`
96
+
97
+ 项目级文件中的字段会覆盖用户级文件中同名字段。配置优先级:调用参数 > 环境变量 > 项目配置 > 用户配置 > 默认值。
98
+
99
+ **示例 `~/.pi/agent/ask-user.json`:**
100
+
101
+ ```json
102
+ {
103
+ "displayMode": "inline",
104
+ "overlayToggleKey": "alt+h",
105
+ "commentToggleKey": "alt+c"
106
+ }
107
+ ```
108
+
109
+ 所有字段可选。文件不存在或 JSON 格式错误时静默忽略,等效于未配置。
110
+
87
111
  ### 快捷键
88
112
 
89
113
  `overlayToggleKey` 和 `commentToggleKey` 的生效优先级:
90
114
 
91
115
  1. 每次调用时的参数(如提供)
92
116
  2. 对应的环境变量(`PI_ASK_USER_OVERLAY_TOGGLE_KEY` / `PI_ASK_USER_COMMENT_TOGGLE_KEY`)
93
- 3. 内置默认值:`alt+o` 和 `ctrl+g`
117
+ 3. 项目配置文件 `.pi/ask-user.json`(如存在)
118
+ 4. 用户配置文件 `~/.pi/agent/ask-user.json`(如存在)
119
+ 5. 内置默认值:`alt+o` 和 `ctrl+g`
94
120
 
95
121
  传入 `"off"`、`"none"` 或 `"disabled"`(任意级别)可以完全禁用快捷键。无效的键位格式会被静默丢弃,使用下一个来源。键位格式遵循 Pi-TUI 的 [`KeyId`](https://github.com/earendil-works/pi-mono/blob/main/packages/tui/src/keys.ts) 格式:`[mod+]...key`,修饰键为 `ctrl`、`shift`、`alt`、`super`,可任意顺序用 `+` 连接(如 `ctrl+g`、`alt+shift+x`、`escape`、`tab`)。
96
122
 
package/index.ts CHANGED
@@ -5,7 +5,10 @@
5
5
  * and a custom box border instead of manual ANSI box drawing.
6
6
  */
7
7
 
8
+ import { existsSync, readFileSync } from "node:fs";
8
9
  import { createRequire } from "node:module";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
9
12
  import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
10
13
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
11
14
  import {
@@ -15,6 +18,7 @@ import {
15
18
  Editor,
16
19
  type EditorTheme,
17
20
  fuzzyFilter,
21
+ isKeyRelease,
18
22
  Key,
19
23
  type Keybinding,
20
24
  type KeybindingsManager,
@@ -317,12 +321,58 @@ function buildShortcut(spec: string): ResolvedShortcut {
317
321
  };
318
322
  }
319
323
 
324
+ interface AskUserConfig {
325
+ displayMode?: string;
326
+ overlayToggleKey?: string;
327
+ commentToggleKey?: string;
328
+ }
329
+
330
+ function filterConfigFields(raw: Record<string, unknown>): Partial<AskUserConfig> {
331
+ const str = (v: unknown): string | undefined => (typeof v === "string" ? v : undefined);
332
+ return {
333
+ displayMode: typeof raw.displayMode === "string" ? raw.displayMode : undefined,
334
+ overlayToggleKey: str(raw.overlayToggleKey),
335
+ commentToggleKey: str(raw.commentToggleKey),
336
+ };
337
+ }
338
+
339
+ function loadAskUserConfig(cwd?: string): Partial<AskUserConfig> {
340
+ const homeDir = homedir();
341
+ const userConfigPath = join(homeDir, ".pi", "agent", "ask-user.json");
342
+
343
+ let config: Partial<AskUserConfig> = {};
344
+ try {
345
+ if (existsSync(userConfigPath)) {
346
+ const raw = readFileSync(userConfigPath, "utf-8");
347
+ config = filterConfigFields(JSON.parse(raw));
348
+ }
349
+ } catch {
350
+ // User config file missing or invalid — silently ignore.
351
+ }
352
+
353
+ if (cwd) {
354
+ const projectConfigPath = join(cwd, ".pi", "ask-user.json");
355
+ try {
356
+ if (existsSync(projectConfigPath)) {
357
+ const raw = readFileSync(projectConfigPath, "utf-8");
358
+ const projectConfig = filterConfigFields(JSON.parse(raw));
359
+ config = { ...config, ...projectConfig };
360
+ }
361
+ } catch {
362
+ // Project config file missing or invalid — silently ignore.
363
+ }
364
+ }
365
+
366
+ return config;
367
+ }
368
+
320
369
  function resolveShortcut(
321
370
  paramValue: string | null | undefined,
322
371
  envValue: string | undefined,
372
+ configValue: string | null | undefined,
323
373
  defaultSpec: string,
324
374
  ): ResolvedShortcut {
325
- const candidates: Array<string | null | undefined> = [paramValue, envValue, defaultSpec];
375
+ const candidates: Array<string | null | undefined> = [paramValue, envValue, configValue, defaultSpec];
326
376
  for (const raw of candidates) {
327
377
  const normalized = normalizeShortcutSpec(raw);
328
378
  if (normalized === undefined) continue; // not provided, fall through
@@ -1565,19 +1615,24 @@ export default function (pi: ExtensionAPI) {
1565
1615
  commentToggleKey,
1566
1616
  timeout,
1567
1617
  } = params as AskParams;
1618
+ const config = loadAskUserConfig(ctx.cwd);
1619
+ const configDisplayMode: AskDisplayMode | undefined =
1620
+ config.displayMode === "overlay" || config.displayMode === "inline" ? config.displayMode : undefined;
1568
1621
  const envMode = process.env.PI_ASK_USER_DISPLAY_MODE;
1569
1622
  const envDisplayMode: AskDisplayMode | undefined =
1570
1623
  envMode === "overlay" || envMode === "inline" ? envMode : undefined;
1571
- const effectiveDisplayMode: AskDisplayMode = displayMode ?? envDisplayMode ?? "overlay";
1624
+ const effectiveDisplayMode: AskDisplayMode = displayMode ?? envDisplayMode ?? configDisplayMode ?? "overlay";
1572
1625
  const shortcuts: ResolvedAskShortcuts = {
1573
1626
  overlayToggle: resolveShortcut(
1574
1627
  overlayToggleKey,
1575
1628
  process.env.PI_ASK_USER_OVERLAY_TOGGLE_KEY,
1629
+ config.overlayToggleKey,
1576
1630
  DEFAULT_OVERLAY_TOGGLE_KEY,
1577
1631
  ),
1578
1632
  commentToggle: resolveShortcut(
1579
1633
  commentToggleKey,
1580
1634
  process.env.PI_ASK_USER_COMMENT_TOGGLE_KEY,
1635
+ config.commentToggleKey,
1581
1636
  DEFAULT_COMMENT_TOGGLE_KEY,
1582
1637
  ),
1583
1638
  };
@@ -1669,6 +1724,12 @@ export default function (pi: ExtensionAPI) {
1669
1724
  if (effectiveDisplayMode === "overlay" && !overlayToggle.disabled && typeof ctx.ui.onTerminalInput === "function") {
1670
1725
  removeOverlayInputListener = ctx.ui.onTerminalInput((data) => {
1671
1726
  if (!overlayToggle.matches(data) || !overlayHandle) return undefined;
1727
+ // Ignore Kitty keyboard protocol key release events.
1728
+ // matchesKittySequence ignores event type, so both press (:1u)
1729
+ // and release (:3u) events match the same key binding. Without
1730
+ // this filter, releasing the toggle key chord would immediately
1731
+ // re-show a just-hidden overlay, causing a visible flicker.
1732
+ if (isKeyRelease(data)) return undefined;
1672
1733
  const nextHidden = !overlayHandle.isHidden();
1673
1734
  overlayHandle.setHidden(nextHidden);
1674
1735
  if (nextHidden && !hasAnnouncedHide) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.0",
6
+ "version": "0.2.0",
7
7
  "description": "Interactive ask_user tool for pi-coding-agent with searchable split-pane selection UI, multi-select, and freeform input",
8
8
  "license": "MIT",
9
9
  "repository": {