@xynogen/pix-hunk 0.1.1 → 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.
Files changed (3) hide show
  1. package/README.md +24 -8
  2. package/package.json +4 -4
  3. package/src/index.ts +29 -22
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # @xynogen/pix-hunk
1
+ # pix-hunk
2
2
 
3
3
  Pi tool for driving a live [Hunk](https://github.com/modem-dev/hunk) diff review.
4
4
  One `hunk` call accepts ordered `ops[]`, so an agent can inspect, navigate,
@@ -7,7 +7,10 @@ quoting.
7
7
 
8
8
  ## Requirements
9
9
 
10
- Install Hunk and launch its interactive TUI in another terminal. This extension
10
+ Launch Hunk's interactive TUI in another terminal. If `hunk` isn't found
11
+ (`~/.pi/agent/binary.json` → `~/.pi/agent/bin` → `PATH`), pix downloads the
12
+ official `modem-dev/hunk` release on first use and verifies it against
13
+ `SHA256SUMS`, with a visible status. This extension
11
14
  only calls `hunk session *`; it never starts `hunk diff`, `show`, `patch`, or
12
15
  another interactive command.
13
16
 
@@ -54,20 +57,33 @@ Model-facing output defaults to 10,000 characters. Set `maxCharacters` between
54
57
  quota, so a large patch cannot hide later results. Full structured results
55
58
  remain available in tool details for inspection.
56
59
 
60
+ ## Scope ceiling
61
+
62
+ `comment apply`, STML markup, and comment clearing are intentionally omitted.
63
+ Use Hunk's CLI directly until repeated demand justifies extending the schema.
64
+
57
65
  ## Install
58
66
 
59
67
  ```bash
60
68
  pi install npm:@xynogen/pix-hunk
61
69
  ```
62
70
 
63
- Standalone/opt-in: Hunk and a live local review session are required, so this
64
- package is not bundled by `@xynogen/pix-core`.
71
+ > Standalone and opt-in. [`@xynogen/pix-core`](https://www.npmjs.com/package/@xynogen/pix-core) does not bundle it. It needs the external Hunk CLI and a live review session.
65
72
 
66
- ## Scope ceiling
73
+ ## Full distro
67
74
 
68
- `comment apply`, STML markup, and comment clearing are intentionally omitted.
69
- Use Hunk's CLI directly until repeated demand justifies extending the schema.
75
+ 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.
76
+
77
+ ```bash
78
+ # Linux / macOS
79
+ curl -fsSL https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.sh | sh
80
+ ```
81
+
82
+ ```powershell
83
+ # Windows
84
+ irm https://raw.githubusercontent.com/xynogen/pix-mono/main/scripts/install.ps1 | iex
85
+ ```
70
86
 
71
87
  ## License
72
88
 
73
- MIT
89
+ MIT. See [LICENSE](LICENSE).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-hunk",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Pi tool — drive live Hunk diff reviews with ordered bulk operations",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -44,11 +44,11 @@
44
44
  "access": "public"
45
45
  },
46
46
  "dependencies": {
47
- "@xynogen/pix-pretty": "^1.19.0",
48
- "@xynogen/pix-runtime": "^0.8.1",
49
- "typebox": "^1.1.38"
47
+ "@xynogen/pix-pretty": "^1.29.0",
48
+ "@xynogen/pix-runtime": "^0.12.0"
50
49
  },
51
50
  "peerDependencies": {
51
+ "typebox": "*",
52
52
  "@earendil-works/pi-coding-agent": "*",
53
53
  "@earendil-works/pi-tui": "*"
54
54
  }
package/src/index.ts CHANGED
@@ -1,13 +1,15 @@
1
- import { execFile } from "node:child_process";
2
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
2
  import { Text } from "@earendil-works/pi-tui";
3
+ import { reportToolStatus, type ToolStatusUI } from "@xynogen/pix-pretty/tool-status";
4
4
  import {
5
5
  dotJoin,
6
6
  formatCollapsedToolRow,
7
7
  frameToolResult,
8
+ getErrorMessage,
8
9
  hideCollapsedToolCall,
9
10
  } from "@xynogen/pix-pretty/utils";
10
11
  import { type CollapseState, tickCollapse } from "@xynogen/pix-runtime/collapse";
12
+ import { runTool } from "@xynogen/pix-runtime/exec";
11
13
  import { Type } from "typebox";
12
14
  import {
13
15
  buildHunkArgs,
@@ -26,22 +28,29 @@ export interface HunkRunResult {
26
28
  code: number;
27
29
  }
28
30
 
29
- export type HunkRunner = (args: string[], signal?: AbortSignal) => Promise<HunkRunResult>;
30
-
31
- export const runHunk: HunkRunner = (args, signal) =>
32
- new Promise((resolve, reject) => {
33
- execFile("hunk", args, { maxBuffer: MAX_OUTPUT_BYTES, signal }, (error, stdout, stderr) => {
34
- if (error && signal?.aborted) {
35
- reject(error);
36
- return;
37
- }
38
- resolve({
39
- stdout,
40
- stderr: stderr || error?.message || "",
41
- code: typeof error?.code === "number" ? error.code : error ? 1 : 0,
42
- });
43
- });
31
+ export type HunkRunner = (
32
+ args: string[],
33
+ signal?: AbortSignal,
34
+ ui?: ToolStatusUI,
35
+ ) => Promise<HunkRunResult>;
36
+
37
+ /**
38
+ * Default runner: resolve hunk via pix-runtime (binary.json → agent bin →
39
+ * PATH), downloading the official release on first use with a visible status.
40
+ * runTool also runs the npm `hunk.cmd` shim on Windows. A missing binary
41
+ * throws BinaryMissingError carrying the install hint.
42
+ */
43
+ export const runHunk: HunkRunner = async (args, signal, ui) => {
44
+ const r = await runTool("hunk", args, {
45
+ signal,
46
+ onStatus: reportToolStatus(ui),
47
+ maxBuffer: MAX_OUTPUT_BYTES,
44
48
  });
49
+ if (signal?.aborted) throw signal.reason ?? new Error("aborted");
50
+ if (r.truncated)
51
+ return { stdout: r.stdout, stderr: `hunk output exceeded ${MAX_OUTPUT_BYTES} bytes`, code: 1 };
52
+ return { stdout: r.stdout, stderr: r.stderr, code: r.code ?? 1 };
53
+ };
45
54
 
46
55
  type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
47
56
 
@@ -361,10 +370,7 @@ function modelText(results: HunkOperationResult[], ops: ToolOp[], maxCharacters:
361
370
  }
362
371
 
363
372
  function executionError(run: HunkRunResult): string {
364
- const raw = (run.stderr || run.stdout || `hunk exited ${run.code}`).trim();
365
- return /(?:^|\s)(?:spawn\s+)?hunk\s+ENOENT(?:\s|$)/i.test(raw)
366
- ? "Hunk CLI not found. Install Hunk, launch a review session, then retry."
367
- : raw;
373
+ return (run.stderr || run.stdout || `hunk exited ${run.code}`).trim();
368
374
  }
369
375
 
370
376
  function actionSummary(actions: HunkAction[]): string {
@@ -440,6 +446,7 @@ function renderExpanded(details: HunkResultDetails, theme: HunkTheme): string {
440
446
  export default function registerHunk(pi: ExtensionAPI, runner: HunkRunner = runHunk): void {
441
447
  pi.registerTool({
442
448
  name: "hunk",
449
+ ...({ exposure: "deferred" } as const),
443
450
  label: "Hunk",
444
451
  renderShell: "self",
445
452
  description:
@@ -505,7 +512,7 @@ export default function registerHunk(pi: ExtensionAPI, runner: HunkRunner = runH
505
512
  const results: HunkOperationResult[] = [];
506
513
  for (const op of ops) {
507
514
  try {
508
- const run = await runner(buildHunkArgs(op, ctx.cwd), signal);
515
+ const run = await runner(buildHunkArgs(op, ctx.cwd), signal, ctx.ui);
509
516
  results.push(
510
517
  run.code === 0
511
518
  ? { action: op.action, ok: true, data: parseOutput(run.stdout || run.stderr) }
@@ -516,7 +523,7 @@ export default function registerHunk(pi: ExtensionAPI, runner: HunkRunner = runH
516
523
  results.push({
517
524
  action: op.action,
518
525
  ok: false,
519
- error: error instanceof Error ? error.message : String(error),
526
+ error: getErrorMessage(error),
520
527
  });
521
528
  }
522
529
  }