@oh-my-pi/pi-natives 9.6.3 → 9.7.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.
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-natives",
3
- "version": "9.6.3",
3
+ "version": "9.7.0",
4
4
  "description": "Native Rust functionality via N-API",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -17,6 +17,7 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "build:native": "bun scripts/build-native.ts",
20
+ "dev:native": "bun scripts/build-native.ts --dev",
20
21
  "check": "biome check . && tsgo -p tsconfig.json",
21
22
  "fix": "biome check --write --unsafe .",
22
23
  "test": "bun run build:native && bun test",
@@ -30,7 +31,7 @@
30
31
  "directory": "packages/natives"
31
32
  },
32
33
  "dependencies": {
33
- "@oh-my-pi/pi-utils": "9.6.3"
34
+ "@oh-my-pi/pi-utils": "9.7.0"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@types/node": "^25.0.10"
package/src/grep/index.ts CHANGED
@@ -34,7 +34,9 @@ export type {
34
34
  * Search files for a regex pattern with optional streaming callback.
35
35
  */
36
36
  export async function grep(options: GrepOptions, onMatch?: (match: GrepMatch) => void): Promise<GrepResult> {
37
- return wrapRequestOptions(() => native.grep(options, onMatch), options);
37
+ // napi-rs ThreadsafeFunction passes (error, value) - skip callback on error
38
+ const cb = onMatch ? (err: Error | null, m: GrepMatch) => !err && onMatch(m) : undefined;
39
+ return wrapRequestOptions(() => native.grep(options, cb), options);
38
40
  }
39
41
 
40
42
  /**
package/src/index.ts CHANGED
@@ -3,9 +3,12 @@
3
3
  */
4
4
 
5
5
  import * as path from "node:path";
6
+ import { setNativeKillTree } from "@oh-my-pi/pi-utils";
6
7
  import type { FindMatch, FindOptions, FindResult } from "./find/types";
7
8
  import { native } from "./native";
8
9
 
10
+ setNativeKillTree(native.killTree);
11
+
9
12
  // =============================================================================
10
13
  // Grep (ripgrep-based regex search)
11
14
  // =============================================================================
@@ -42,6 +45,9 @@ export async function find(options: FindOptions, onMatch?: (match: FindMatch) =>
42
45
  // Convert simple patterns to recursive globs if needed
43
46
  const globPattern = pattern.includes("/") || pattern.startsWith("**") ? pattern : `**/${pattern}`;
44
47
 
48
+ // napi-rs ThreadsafeFunction passes (error, value) - skip callback on error
49
+ const cb = onMatch ? (err: Error | null, m: FindMatch) => !err && onMatch(m) : undefined;
50
+
45
51
  return native.find(
46
52
  {
47
53
  ...options,
@@ -50,7 +56,7 @@ export async function find(options: FindOptions, onMatch?: (match: FindMatch) =>
50
56
  hidden: options.hidden ?? false,
51
57
  gitignore: options.gitignore ?? true,
52
58
  },
53
- onMatch,
59
+ cb,
54
60
  );
55
61
  }
56
62
 
@@ -94,7 +100,15 @@ export {
94
100
  // Keyboard sequence helpers
95
101
  // =============================================================================
96
102
 
97
- export { matchesKittySequence } from "./keys/index";
103
+ export {
104
+ type KeyEventType,
105
+ matchesKey,
106
+ matchesKittySequence,
107
+ matchesLegacySequence,
108
+ type ParsedKittyResult,
109
+ parseKey,
110
+ parseKittySequence,
111
+ } from "./keys/index";
98
112
 
99
113
  // =============================================================================
100
114
  // HTML to Markdown
@@ -105,4 +119,32 @@ export {
105
119
  htmlToMarkdown,
106
120
  } from "./html/index";
107
121
 
108
- export type { RequestOptions } from "./request-options";
122
+ // =============================================================================
123
+ // Process management
124
+ // =============================================================================
125
+
126
+ /**
127
+ * Kill a process and all its descendants.
128
+ *
129
+ * Uses platform-native APIs for efficiency:
130
+ * - Linux: /proc/{pid}/children
131
+ * - macOS: libproc (proc_listchildpids)
132
+ * - Windows: CreateToolhelp32Snapshot
133
+ *
134
+ * @param pid - Process ID to kill
135
+ * @param signal - Signal number (e.g., 9 for SIGKILL). Ignored on Windows.
136
+ * @returns Number of processes successfully killed
137
+ */
138
+ export function killTree(pid: number, signal: number): number {
139
+ return native.killTree(pid, signal);
140
+ }
141
+
142
+ /**
143
+ * List all descendant PIDs of a process.
144
+ *
145
+ * @param pid - Process ID to query
146
+ * @returns Array of descendant PIDs (children, grandchildren, etc.)
147
+ */
148
+ export function listDescendants(pid: number): number[] {
149
+ return native.listDescendants(pid);
150
+ }
package/src/keys/index.ts CHANGED
@@ -2,9 +2,63 @@
2
2
  * Keyboard sequence utilities powered by native bindings.
3
3
  */
4
4
 
5
- import { native } from "../native";
5
+ import { type KeyEventType, native, type ParsedKittyResult } from "../native";
6
+
7
+ export type { KeyEventType, ParsedKittyResult };
6
8
 
7
9
  /** Match Kitty protocol sequences for codepoint and modifier. */
8
10
  export function matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean {
9
11
  return native.matchesKittySequence(data, expectedCodepoint, expectedModifier);
10
12
  }
13
+
14
+ /**
15
+ * Parse a Kitty keyboard protocol sequence.
16
+ *
17
+ * @param data - Raw escape sequence from terminal
18
+ * @returns Parsed sequence with codepoint, modifier, and event type, or undefined if not a valid Kitty sequence
19
+ */
20
+ export function parseKittySequence(data: string): ParsedKittyResult | undefined {
21
+ return native.parseKittySequence(data) ?? undefined;
22
+ }
23
+
24
+ /**
25
+ * Parse terminal input and return a normalized key identifier.
26
+ *
27
+ * Returns key names like "escape", "ctrl+c", "shift+tab", "alt+enter".
28
+ * Returns undefined if the input is not a recognized key sequence.
29
+ *
30
+ * @param data - Raw input data from terminal
31
+ * @param kittyProtocolActive - Whether Kitty keyboard protocol is active
32
+ */
33
+ export function parseKey(data: string, kittyProtocolActive: boolean): string | undefined {
34
+ return native.parseKey(data, kittyProtocolActive) ?? undefined;
35
+ }
36
+
37
+ /**
38
+ * Check if input matches a legacy escape sequence for a specific key.
39
+ *
40
+ * @param data - Raw input data from terminal
41
+ * @param keyName - Key name to match (e.g., "up", "f1", "ctrl+up")
42
+ */
43
+ export function matchesLegacySequence(data: string, keyName: string): boolean {
44
+ return native.matchesLegacySequence(data, keyName);
45
+ }
46
+
47
+ /**
48
+ * Match input data against a key identifier string.
49
+ *
50
+ * Supported key identifiers:
51
+ * - Single keys: "escape", "tab", "enter", "backspace", "delete", "home", "end", "space"
52
+ * - Arrow keys: "up", "down", "left", "right"
53
+ * - Ctrl combinations: "ctrl+c", "ctrl+z", etc.
54
+ * - Shift combinations: "shift+tab", "shift+enter"
55
+ * - Alt combinations: "alt+enter", "alt+backspace"
56
+ * - Combined modifiers: "shift+ctrl+p", "ctrl+alt+x"
57
+ *
58
+ * @param data - Raw input data from terminal
59
+ * @param keyId - Key identifier (e.g., "ctrl+c", "escape")
60
+ * @param kittyProtocolActive - Whether Kitty keyboard protocol is active
61
+ */
62
+ export function matchesKey(data: string, keyId: string, kittyProtocolActive: boolean): boolean {
63
+ return native.matchesKey(data, keyId, kittyProtocolActive);
64
+ }
package/src/native.ts CHANGED
@@ -13,6 +13,26 @@ import type { HighlightColors } from "./highlight/index";
13
13
  import type { HtmlToMarkdownOptions } from "./html/types";
14
14
  import type { ExtractSegmentsResult, SliceWithWidthResult } from "./text/index";
15
15
 
16
+ export type { RequestOptions } from "./request-options";
17
+
18
+ /**
19
+ * Event types from Kitty keyboard protocol (flag 2)
20
+ * 1 = key press, 2 = key repeat, 3 = key release
21
+ */
22
+ export const enum KeyEventType {
23
+ Press = 1,
24
+ Repeat = 2,
25
+ Release = 3,
26
+ }
27
+ /** Parsed Kitty keyboard protocol sequence result. */
28
+ export interface ParsedKittyResult {
29
+ codepoint: number;
30
+ shiftedKey?: number;
31
+ baseLayoutKey?: number;
32
+ modifier: number;
33
+ eventType?: KeyEventType;
34
+ }
35
+
16
36
  export interface NativePhotonImage {
17
37
  getWidth(): number;
18
38
  getHeight(): number;
@@ -39,9 +59,9 @@ export interface NativeSamplingFilter {
39
59
  import type { GrepMatch } from "./grep/types";
40
60
 
41
61
  export interface NativeBindings {
42
- find(options: FindOptions, onMatch?: (match: FindMatch) => void): Promise<FindResult>;
62
+ find(options: FindOptions, onMatch?: (error: Error | null, match: FindMatch) => void): Promise<FindResult>;
43
63
  fuzzyFind(options: FuzzyFindOptions): Promise<FuzzyFindResult>;
44
- grep(options: GrepOptions, onMatch?: (match: GrepMatch) => void): Promise<GrepResult>;
64
+ grep(options: GrepOptions, onMatch?: (error: Error | null, match: GrepMatch) => void): Promise<GrepResult>;
45
65
  search(content: string | Uint8Array, options: SearchOptions): SearchResult;
46
66
  hasMatch(
47
67
  content: string | Uint8Array,
@@ -66,6 +86,12 @@ export interface NativeBindings {
66
86
  strictAfter: boolean,
67
87
  ): ExtractSegmentsResult;
68
88
  matchesKittySequence(data: string, expectedCodepoint: number, expectedModifier: number): boolean;
89
+ parseKey(data: string, kittyProtocolActive: boolean): string | null;
90
+ matchesLegacySequence(data: string, keyName: string): boolean;
91
+ parseKittySequence(data: string): ParsedKittyResult | null;
92
+ matchesKey(data: string, keyId: string, kittyProtocolActive: boolean): boolean;
93
+ killTree(pid: number, signal: number): number;
94
+ listDescendants(pid: number): number[];
69
95
  }
70
96
 
71
97
  const require = createRequire(import.meta.url);
@@ -76,15 +102,19 @@ const execDir = path.dirname(process.execPath);
76
102
 
77
103
  const SUPPORTED_PLATFORMS = ["linux-x64", "linux-arm64", "darwin-x64", "darwin-arm64", "win32-x64"];
78
104
 
79
- const candidates = [
80
- path.join(repoRoot, "target", "release", "pi_natives.node"),
81
- path.join(repoRoot, "crates", "pi-natives", "target", "release", "pi_natives.node"),
105
+ const debugCandidates = [path.join(nativeDir, "pi_natives.dev.node"), path.join(execDir, "pi_natives.dev.node")];
106
+
107
+ const releaseCandidates = [
108
+ // Platform-tagged builds (preferred - always correct platform)
82
109
  path.join(nativeDir, `pi_natives.${platformTag}.node`),
83
- path.join(nativeDir, "pi_natives.node"),
84
110
  path.join(execDir, `pi_natives.${platformTag}.node`),
111
+ // Fallback untagged (only created for native builds, not cross-compilation)
112
+ path.join(nativeDir, "pi_natives.node"),
85
113
  path.join(execDir, "pi_natives.node"),
86
114
  ];
87
115
 
116
+ const candidates = process.env.OMP_DEV ? [...debugCandidates, ...releaseCandidates] : releaseCandidates;
117
+
88
118
  function loadNative(): NativeBindings {
89
119
  const errors: string[] = [];
90
120
 
@@ -92,8 +122,15 @@ function loadNative(): NativeBindings {
92
122
  try {
93
123
  const bindings = require(candidate) as NativeBindings;
94
124
  validateNative(bindings, candidate);
125
+ if (process.env.OMP_DEV) {
126
+ console.log(`Loaded native addon from ${candidate}`);
127
+ console.log(` - Root: ${repoRoot}`);
128
+ }
95
129
  return bindings;
96
130
  } catch (err) {
131
+ if (process.env.OMP_DEV) {
132
+ console.error(`Error loading native addon from ${candidate}:`, err);
133
+ }
97
134
  const message = err instanceof Error ? err.message : String(err);
98
135
  errors.push(`${candidate}: ${message}`);
99
136
  }
@@ -138,6 +175,13 @@ function validateNative(bindings: NativeBindings, source: string): void {
138
175
  checkFn("sliceWithWidth");
139
176
  checkFn("extractSegments");
140
177
  checkFn("matchesKittySequence");
178
+ checkFn("parseKey");
179
+ checkFn("matchesLegacySequence");
180
+ checkFn("parseKittySequence");
181
+ checkFn("matchesKey");
182
+ checkFn("visibleWidth");
183
+ checkFn("killTree");
184
+ checkFn("listDescendants");
141
185
 
142
186
  if (missing.length) {
143
187
  throw new Error(