@xynogen/pix-search 0.3.0 → 0.3.3

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
@@ -32,6 +32,22 @@ boundary `@` opens the picker.
32
32
  pi install npm:@xynogen/pix-search
33
33
  ```
34
34
 
35
- Standalone — **not** bundled by `@xynogen/pix-core`. It wraps whatever editor
36
- is already installed (via `getEditorComponent`/`setEditorComponent`), so it
37
- composes with `pix-display` in either load order.
35
+ > Standalone and opt-in. [`@xynogen/pix-core`](https://www.npmjs.com/package/@xynogen/pix-core) does not bundle it. It replaces Pi's built-in `@` autocomplete. It wraps the installed editor, so it works with `pix-display` in either load order.
36
+
37
+ ## Full distro
38
+
39
+ This package is part of [Pix](https://github.com/xynogen/pix-mono). The installer sets up Pi and the full distro. See [Install](https://github.com/xynogen/pix-mono#install) for the notes for each OS.
40
+
41
+ ```bash
42
+ # Linux / macOS
43
+ curl -fsSL https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.sh | sh
44
+ ```
45
+
46
+ ```powershell
47
+ # Windows
48
+ irm https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.ps1 | iex
49
+ ```
50
+
51
+ ## License
52
+
53
+ MIT. See [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xynogen/pix-search",
3
- "version": "0.3.0",
4
- "description": "Pi extension — @ file picker overlay with fuzzy + git recency ranking",
3
+ "version": "0.3.3",
4
+ "description": "Pi extension \u2014 @ file picker overlay with fuzzy + git recency ranking",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "exports": {
@@ -49,6 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@vscode/ripgrep": "^1.18.0",
52
- "@xynogen/pix-pretty": "^1.21.0"
52
+ "@xynogen/pix-pretty": "^1.30.0",
53
+ "@xynogen/pix-runtime": "^0.13.0"
53
54
  }
54
55
  }
package/src/editor.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  /** Add the @ picker to an editor without replacing its paste state or rendering. */
2
2
  import type { CustomEditor } from "@earendil-works/pi-coding-agent";
3
3
  import type { TUI } from "@earendil-works/pi-tui";
4
+ import { chipTag } from "@xynogen/pix-pretty/chips";
5
+ import { getErrorMessage } from "@xynogen/pix-pretty/utils";
4
6
 
5
- /** Picked paths are marked as `<path>…</path>`; pix-display renders the span as a chip. */
7
+ /** Picked paths use the shared `<path>` chip tag. pix-display renders it as a chip. */
6
8
  export function pathToken(path: string): string {
7
- return `<path>${path}</path> `;
9
+ return `${chipTag.path(path)} `;
8
10
  }
9
11
 
10
12
  export type OpenPicker = () => Promise<string | null>;
@@ -28,7 +30,7 @@ export function attachPicker(
28
30
  editor.insertTextAtCursor(path ? pathToken(path) : "@");
29
31
  } catch (error) {
30
32
  editor.insertTextAtCursor("@");
31
- onError(`File picker failed: ${error instanceof Error ? error.message : String(error)}`);
33
+ onError(`File picker failed: ${getErrorMessage(error)}`);
32
34
  } finally {
33
35
  tui.requestRender();
34
36
  }
package/src/extension.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { CustomEditor, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
10
+ import { warnBinaryMissing } from "@xynogen/pix-pretty/tool-status";
10
11
  import { showTransientMessage } from "@xynogen/pix-pretty/transient-error";
11
12
  import { attachPicker } from "./editor.ts";
12
13
  import { FilePicker } from "./picker.ts";
@@ -22,9 +23,12 @@ export default function (pi: ExtensionAPI): void {
22
23
  // Warm the git-recency cache in the background; picker reads whatever is
23
24
  // ready when it opens (empty until the first git log returns).
24
25
  let recency: RecencyMap = new Map();
25
- void loadRecency(cwd).then((map) => {
26
- recency = map;
27
- });
26
+ void loadRecency(cwd).then(
27
+ (map) => {
28
+ recency = map;
29
+ },
30
+ (err: unknown) => warnBinaryMissing(ctx.ui, err),
31
+ );
28
32
 
29
33
  // Open the picker overlay and resolve with the chosen path (or null).
30
34
  const openPicker = () =>
package/src/recency.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Cached per session, refreshed on demand.
5
5
  */
6
6
 
7
- import { execFile } from "node:child_process";
7
+ import { runGit } from "@xynogen/pix-runtime/os";
8
8
 
9
9
  const MAX_COMMITS = 200;
10
10
  const DECAY = 0.97; // exponential decay per commit position
@@ -28,32 +28,16 @@ export function buildRecencyScores(gitOutput: string): RecencyMap {
28
28
  return scores;
29
29
  }
30
30
 
31
+ /**
32
+ * Recency scores for files in `cwd` (empty outside a repo, on abort, or on
33
+ * git failure). A missing git binary rejects with BinaryMissingError so the
34
+ * caller can show the install hint once.
35
+ */
31
36
  export async function loadRecency(cwd: string, signal?: AbortSignal): Promise<RecencyMap> {
32
- return new Promise((resolve) => {
33
- if (signal?.aborted) {
34
- resolve(new Map());
35
- return;
36
- }
37
-
38
- const child = execFile(
39
- "git",
40
- ["log", "--name-only", "--pretty=format:", `-n${MAX_COMMITS}`, "--diff-filter=ACMR"],
41
- { cwd, maxBuffer: 2 * 1024 * 1024, signal },
42
- (err, stdout) => {
43
- if (err || !stdout) {
44
- resolve(new Map());
45
- return;
46
- }
47
- resolve(buildRecencyScores(stdout));
48
- },
49
- );
50
-
51
- signal?.addEventListener(
52
- "abort",
53
- () => {
54
- child.kill("SIGKILL");
55
- },
56
- { once: true },
57
- );
58
- });
37
+ if (signal?.aborted) return new Map();
38
+ const stdout = await runGit(
39
+ ["log", "--name-only", "--pretty=format:", `-n${MAX_COMMITS}`, "--diff-filter=ACMR"],
40
+ { cwd, signal, timeoutMs: 15_000, maxBuffer: 2 * 1024 * 1024 },
41
+ );
42
+ return stdout ? buildRecencyScores(stdout) : new Map();
59
43
  }
package/src/rg.ts CHANGED
@@ -7,16 +7,25 @@
7
7
 
8
8
  import { spawn } from "node:child_process";
9
9
  import { basename } from "node:path";
10
+ import { resolveTool } from "@xynogen/pix-runtime/binaries";
10
11
 
11
- // Bundled ripgrep: @vscode/ripgrep pulls a prebuilt rg per-platform on install
12
- // and exposes its absolute path, so users no longer need rg on PATH. Fall back
13
- // to a bare "rg" (PATH lookup) if the postinstall binary download was skipped
14
- // (e.g. npm_config_ignore_scripts).
15
- let RG_BIN = "rg";
16
- try {
17
- RG_BIN = (require("@vscode/ripgrep") as { rgPath: string }).rgPath;
18
- } catch {
19
- /* bundled binary unavailable — fall back to PATH `rg` */
12
+ /**
13
+ * rg resolution: pix-runtime (binary.json → <agentDir>/bin, where Pi downloads
14
+ * rg → PATH). `@vscode/ripgrep`'s bundled copy is only the last fallback, for
15
+ * hosts where Pi has not fetched rg yet. Resolved lazily, once per process.
16
+ */
17
+ let rgBin: string | undefined;
18
+ export function rgBinary(): string {
19
+ if (rgBin) return rgBin;
20
+ rgBin = resolveTool("rg")?.path;
21
+ if (!rgBin) {
22
+ try {
23
+ rgBin = (require("@vscode/ripgrep") as { rgPath: string }).rgPath;
24
+ } catch {
25
+ rgBin = "rg";
26
+ }
27
+ }
28
+ return rgBin;
20
29
  }
21
30
 
22
31
  const TIMEOUT_MS = 3_000;
@@ -28,7 +37,7 @@ function spawnRg(args: string[], cwd: string, signal: AbortSignal): Promise<stri
28
37
  return;
29
38
  }
30
39
 
31
- const child = spawn(RG_BIN, args, {
40
+ const child = spawn(rgBinary(), args, {
32
41
  cwd,
33
42
  stdio: ["ignore", "pipe", "ignore"],
34
43
  // ponytail: no env filtering — inherits .gitignore respect from rg defaults