pi-ui-extend 1.0.45 → 1.0.47

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
@@ -512,10 +512,21 @@ Pix implements the Pi extension UI surface for notifications, keyed toasts, widg
512
512
 
513
513
  ## Development
514
514
 
515
+ Use the exact Node.js version in `.node-version` for development and builds;
516
+ `.nvmrc` mirrors it for nvm users. No particular version manager is required:
517
+ all npm scripts use the `node` and `npm` already on your `PATH`.
518
+ The supported runtime range is `>=22.19.0 <25`, shared by Pix, ACP, and Desktop.
519
+ CI checks the pinned version on Linux, macOS, and Windows, plus the minimum
520
+ supported version on Linux.
521
+
515
522
  ```bash
516
523
  git clone https://github.com/dnaroid/pix.git
517
524
  cd pix
518
- npm install
525
+
526
+ # Select the version from .node-version using your preferred installation method.
527
+ # With nvm (optional): nvm install && nvm use
528
+ node --version
529
+ npm ci
519
530
 
520
531
  # Run from source
521
532
  npm run dev -- --cwd /path/to/project
package/bin/pix.mjs CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync } from "node:fs";
2
+ import { existsSync, readFileSync } from "node:fs";
3
3
  import { delimiter, dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
- const minimumNodeVersion = [22, 19, 0];
7
- const minimumNodeVersionLabel = "22.19.0";
6
+ const supportedNodeRange = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).engines.node;
8
7
  const launcherPath = fileURLToPath(import.meta.url);
9
8
  const packageRoot = dirname(dirname(launcherPath));
10
9
  const mainPath = fileURLToPath(new URL("../dist/main.js", import.meta.url));
@@ -13,8 +12,8 @@ const installPath = fileURLToPath(new URL("../dist/app/cli/install.js", import.m
13
12
  const cliArgs = process.argv.slice(2);
14
13
 
15
14
  if (!isCurrentNodeSupported()) {
16
- console.error(`[pix] Node ${minimumNodeVersionLabel}+ is required; current Node is ${process.versions.node}.`);
17
- console.error("[pix] Install/use a newer Node, for example `mise install node@22.19.0` or `nvm install 22`.");
15
+ console.error(`[pix] Node ${supportedNodeRange} is required; current Node is ${process.versions.node}.`);
16
+ console.error("[pix] Install a supported Node.js release and ensure node and npm use it on PATH.");
18
17
  process.exit(1);
19
18
  }
20
19
 
@@ -45,9 +44,15 @@ if (!existsSync(mainPath)) {
45
44
  await import(new URL("../dist/main.js", import.meta.url));
46
45
 
47
46
  function isCurrentNodeSupported() {
48
- const parts = process.versions.node.split(".").map((part) => Number.parseInt(part, 10));
47
+ // Only the project's bounded stable range is accepted; fail closed if its format changes.
48
+ const range = /^>=(\d+)\.(\d+)\.(\d+) <(\d+)$/u.exec(supportedNodeRange);
49
+ const version = /^(\d+)\.(\d+)\.(\d+)$/u.exec(process.versions.node);
50
+ if (!range || !version) return false;
51
+ const minimumNodeVersion = range.slice(1, 4).map(Number);
52
+ const parts = version.slice(1).map(Number);
53
+ if (parts[0] >= Number(range[4])) return false;
49
54
  for (let index = 0; index < minimumNodeVersion.length; index += 1) {
50
- const current = parts[index] ?? 0;
55
+ const current = parts[index];
51
56
  const minimum = minimumNodeVersion[index];
52
57
  if (current > minimum) return true;
53
58
  if (current < minimum) return false;
package/dist/app/app.d.ts CHANGED
@@ -80,6 +80,7 @@ export declare class PiUiExtendApp {
80
80
  private selectDraftModel;
81
81
  private draftModelState;
82
82
  private draftModelStatus;
83
+ private draftModelUsageSelection;
83
84
  private applyPixConfig;
84
85
  private updateOutputFilters;
85
86
  start(): Promise<void>;
package/dist/app/app.js CHANGED
@@ -185,6 +185,7 @@ export class PiUiExtendApp {
185
185
  });
186
186
  this.modelUsageController = new AppModelUsageController({
187
187
  runtimeSession: () => this.runtime?.session,
188
+ draftSelection: () => this.draftModelUsageSelection(),
188
189
  render: () => this.render(),
189
190
  });
190
191
  this.tabsController = new AppTabsController({
@@ -948,6 +949,7 @@ export class PiUiExtendApp {
948
949
  this.draftModelRef = ref;
949
950
  this.draftThinkingLevel = thinkingLevel;
950
951
  this.draftModelOverrideRef = `${ref}:${thinkingLevel}`;
952
+ this.modelUsageController.observeSession(undefined);
951
953
  this.setStatus("new conversation");
952
954
  }
953
955
  draftModelState() {
@@ -965,6 +967,13 @@ export class PiUiExtendApp {
965
967
  return undefined;
966
968
  return { modelLabel: state.modelRef, thinkingLabel: state.thinkingLevel };
967
969
  }
970
+ draftModelUsageSelection() {
971
+ const state = this.draftModelState();
972
+ if (!state?.modelRef)
973
+ return undefined;
974
+ const model = state.models.find((candidate) => `${candidate.provider}/${candidate.id}` === state.modelRef);
975
+ return model ? { model, thinkingLevel: state.thinkingLevel } : undefined;
976
+ }
968
977
  applyPixConfig(config) {
969
978
  replaceRecord(this.pixConfig.toolRenderer, config.toolRenderer);
970
979
  replaceRecord(this.pixConfig.outputFilters, config.outputFilters);
@@ -384,10 +384,8 @@ export class ModelCommandActions {
384
384
  });
385
385
  }
386
386
  saveDefaultThinking(level) {
387
- const runtime = getRuntime(this.host, "default-thinking");
388
- if (!runtime)
389
- return;
390
- const fallbackModelRef = runtime.session.model ? this.host.modelRef(runtime.session.model) : undefined;
387
+ const runtime = this.host.runtime();
388
+ const fallbackModelRef = runtime?.session.model ? this.host.modelRef(runtime.session.model) : undefined;
391
389
  const saved = savePixDefaultThinking(level, fallbackModelRef);
392
390
  if (!saved)
393
391
  throw new Error("Set /default-model first or select a session model before setting default thinking");
@@ -396,7 +394,7 @@ export class ModelCommandActions {
396
394
  kind: "system",
397
395
  text: `Default thinking level set to ${saved.thinking ?? level} for ${saved.modelRef}. New sessions will use it unless --model is provided.`,
398
396
  });
399
- this.host.setSessionStatus(runtime.session);
397
+ this.host.setSessionStatus(runtime?.session);
400
398
  }
401
399
  }
402
400
  function formatDefaultModelRef(defaultModel) {
@@ -1,4 +1,5 @@
1
1
  import type { AgentSession } from "@earendil-works/pi-coding-agent";
2
+ import type { SessionModel } from "../types.js";
2
3
  import { type ModelUsageDescriptor, type ModelUsageStatus } from "./model-usage-status.js";
3
4
  export type AppModelUsageQuery = (descriptor: ModelUsageDescriptor) => Promise<ModelUsageStatus | undefined>;
4
5
  export type ModelUsageRefreshResult = "refreshed" | "unavailable" | "failed";
@@ -12,6 +13,10 @@ export type ModelUsageRefreshStart = {
12
13
  };
13
14
  export type AppModelUsageControllerHost = {
14
15
  runtimeSession(): AgentSession | undefined;
16
+ draftSelection?(): {
17
+ model: SessionModel;
18
+ thinkingLevel: string;
19
+ } | undefined;
15
20
  render(): void;
16
21
  };
17
22
  export declare class AppModelUsageController {
@@ -32,4 +37,5 @@ export declare class AppModelUsageController {
32
37
  private refresh;
33
38
  private syncActiveModel;
34
39
  private activeStatus;
40
+ private activeDescriptor;
35
41
  }
@@ -45,7 +45,7 @@ export class AppModelUsageController {
45
45
  refreshNow() {
46
46
  const session = this.host.runtimeSession();
47
47
  this.syncActiveModel(session);
48
- const descriptor = modelUsageDescriptor(session?.model, session?.thinkingLevel);
48
+ const descriptor = this.activeDescriptor(session);
49
49
  if (!descriptor)
50
50
  return { kind: "unsupported" };
51
51
  const promise = this.refresh(true, descriptor);
@@ -54,7 +54,7 @@ export class AppModelUsageController {
54
54
  tick(force = false) {
55
55
  const session = this.host.runtimeSession();
56
56
  this.syncActiveModel(session);
57
- const descriptor = modelUsageDescriptor(session?.model, session?.thinkingLevel);
57
+ const descriptor = this.activeDescriptor(session);
58
58
  if (!descriptor)
59
59
  return;
60
60
  const cacheKey = descriptorCacheKey(descriptor);
@@ -68,7 +68,7 @@ export class AppModelUsageController {
68
68
  }
69
69
  refresh(force = false, activeDescriptor) {
70
70
  const session = this.host.runtimeSession();
71
- const descriptor = activeDescriptor ?? modelUsageDescriptor(session?.model, session?.thinkingLevel);
71
+ const descriptor = activeDescriptor ?? this.activeDescriptor(session);
72
72
  if (!descriptor)
73
73
  return undefined;
74
74
  const modelKey = descriptorCacheKey(descriptor);
@@ -96,7 +96,7 @@ export class AppModelUsageController {
96
96
  });
97
97
  }
98
98
  syncActiveModel(session) {
99
- const nextModelKey = descriptorCacheKey(modelUsageDescriptor(session?.model, session?.thinkingLevel));
99
+ const nextModelKey = descriptorCacheKey(this.activeDescriptor(session));
100
100
  if (nextModelKey === this.activeModelKey)
101
101
  return false;
102
102
  this.activeModelKey = nextModelKey;
@@ -106,4 +106,10 @@ export class AppModelUsageController {
106
106
  activeStatus() {
107
107
  return this.activeModelKey ? this.statuses.get(this.activeModelKey) : undefined;
108
108
  }
109
+ activeDescriptor(session) {
110
+ if (session)
111
+ return modelUsageDescriptor(session.model, session.thinkingLevel);
112
+ const draft = this.host.draftSelection?.();
113
+ return draft ? modelUsageDescriptor(draft.model, draft.thinkingLevel) : undefined;
114
+ }
109
115
  }
@@ -325,6 +325,30 @@ export async function runQuestionnaire(questions, ctx) {
325
325
  }
326
326
  showReview();
327
327
  }
328
+ function confirmMultipleAnswer() {
329
+ const question = currentQuestion();
330
+ if (!question.multiple)
331
+ return;
332
+ const selection = selections.get(question.id);
333
+ if (!selection || !selectionIsComplete(question, selection)) {
334
+ const existing = multipleSelection(question);
335
+ if (existing.customText !== undefined && !existing.customText.trim() && (existing.images?.length ?? 0) === 0) {
336
+ customError = "Custom Answer cannot be empty.";
337
+ }
338
+ else {
339
+ const minimum = question.minSelections ?? 1;
340
+ customError = `Choose at least ${minimum} answer${minimum === 1 ? "" : "s"}.`;
341
+ }
342
+ refresh();
343
+ return;
344
+ }
345
+ customError = undefined;
346
+ if (questionIndex < questions.length - 1) {
347
+ moveToQuestion(questionIndex + 1);
348
+ return;
349
+ }
350
+ submitOrAnswerRemaining();
351
+ }
328
352
  function enterCustomMode() {
329
353
  const question = currentQuestion();
330
354
  const existing = selections.get(question.id);
@@ -410,7 +434,7 @@ export async function runQuestionnaire(questions, ctx) {
410
434
  mode = "choices";
411
435
  customError = undefined;
412
436
  syncChoiceSelection();
413
- refresh();
437
+ confirmMultipleAnswer();
414
438
  return;
415
439
  }
416
440
  selections.set(question.id, {
@@ -558,7 +582,17 @@ export async function runQuestionnaire(questions, ctx) {
558
582
  goBack();
559
583
  return;
560
584
  }
561
- if (isKey(data, "enter") || data === " ") {
585
+ if (currentQuestion().multiple) {
586
+ if (data === " ") {
587
+ selectChoice(selectedChoiceIndex);
588
+ return;
589
+ }
590
+ if (isKey(data, "enter")) {
591
+ confirmMultipleAnswer();
592
+ return;
593
+ }
594
+ }
595
+ else if (isKey(data, "enter") || data === " ") {
562
596
  selectChoice(selectedChoiceIndex);
563
597
  return;
564
598
  }
@@ -36,6 +36,7 @@ import {
36
36
  } from "./compression-preview.js"
37
37
  import { settleCompressionProgress } from "./compression-progress.js"
38
38
  import { estimateMessageTokens } from "./pruner-metadata.js"
39
+ import { incompleteToolGroupGuidance } from "./protocol-closed-ranges.js"
39
40
  import {
40
41
  buildSummarySourceManifest,
41
42
  hashSummarySourceManifest,
@@ -190,7 +191,7 @@ function validateProtocolClosedRanges(plans: ResolvedRangeBoundaries[], state: D
190
191
  if (closure.incompleteToolGroup) {
191
192
  throw new Error(
192
193
  `Compression range ${plan.startId}..${plan.endId} intersects an incomplete tool group. ` +
193
- "Wait until the complete assistant/tool-result group is present or choose another closed range.",
194
+ incompleteToolGroupGuidance(state.conversationIndexSnapshot, plan.startId, plan.endId),
194
195
  )
195
196
  }
196
197
  if (closure.expanded) {
@@ -0,0 +1,90 @@
1
+ import { closeConversationRange, detectToolGroupSpans } from "./conversation-index.js";
2
+ import type { ConversationIndexEntry } from "./state.js";
3
+
4
+ interface RangeBoundary {
5
+ id: string;
6
+ messageIndex: number;
7
+ }
8
+
9
+ function diagnosticId(id: string): string {
10
+ return id.length > 160 ? `${id.slice(0, 157)}...` : id;
11
+ }
12
+
13
+ /**
14
+ * Maximal closed runs inside an already policy-filtered selection. Incomplete
15
+ * groups are barriers, not reasons to discard all later completed work. Never
16
+ * widen across a retention boundary or an excluded message/block to close one.
17
+ */
18
+ export function protocolClosedBoundaryRuns<T extends RangeBoundary>(
19
+ boundaries: T[],
20
+ entries: ConversationIndexEntry[],
21
+ ): T[][] {
22
+ if (boundaries.length === 0 || entries.length === 0) return [];
23
+ const first = boundaries[0]!.messageIndex;
24
+ const last = boundaries[boundaries.length - 1]!.messageIndex;
25
+ const included = new Set(boundaries.map((boundary) => boundary.messageIndex));
26
+ const excluded = new Set(entries.filter((entry) =>
27
+ entry.index >= first && entry.index <= last && !included.has(entry.index) &&
28
+ !entry.passthrough && entry.origin !== "dcp-control",
29
+ ).map((entry) => entry.index));
30
+ const barriers = [...excluded].map((index) => ({ startIndex: index, endIndex: index }));
31
+ for (const group of detectToolGroupSpans(entries)) {
32
+ if (group.endIndex < first || group.startIndex > last) continue;
33
+ if (!group.complete || group.startIndex < first || group.endIndex > last ||
34
+ entries.slice(group.startIndex, group.endIndex + 1).some((entry) => excluded.has(entry.index))) {
35
+ barriers.push(group);
36
+ }
37
+ }
38
+ barriers.sort((a, b) => a.startIndex - b.startIndex);
39
+
40
+ const runs: T[][] = [];
41
+ let cursor = 0;
42
+ for (const barrier of barriers) {
43
+ const start = cursor;
44
+ while (cursor < boundaries.length && boundaries[cursor]!.messageIndex < barrier.startIndex) cursor++;
45
+ if (cursor > start) runs.push(boundaries.slice(start, cursor));
46
+ while (cursor < boundaries.length && boundaries[cursor]!.messageIndex <= barrier.endIndex) cursor++;
47
+ }
48
+ if (cursor < boundaries.length) runs.push(boundaries.slice(cursor));
49
+
50
+ // Use the executor's closure authority, including non-addressable entries.
51
+ return runs.filter((run) => {
52
+ const closure = closeConversationRange(entries, run[0]!.id, run[run.length - 1]!.id);
53
+ return closure && !closure.expanded && !closure.incompleteToolGroup;
54
+ });
55
+ }
56
+
57
+ /** Bounded recovery guidance for stale/manual selections; never changes them. */
58
+ export function incompleteToolGroupGuidance(
59
+ entries: ConversationIndexEntry[],
60
+ startId: string,
61
+ endId: string,
62
+ ): string {
63
+ const closure = closeConversationRange(entries, startId, endId);
64
+ if (!closure) return "Choose another protocol-closed range.";
65
+ const blockers = detectToolGroupSpans(entries).filter((group) =>
66
+ !group.complete && group.startIndex <= closure.endIndex && group.endIndex >= closure.startIndex,
67
+ );
68
+ const details = blockers.slice(0, 3).map((group) => {
69
+ const assistant = entries[group.startIndex]!;
70
+ const results = new Set(entries.slice(group.startIndex + 1, group.endIndex + 1)
71
+ .filter((entry) => entry.role === "toolResult" || entry.role === "bashExecution")
72
+ .map((entry) => entry.toolCallId));
73
+ const missing = group.toolCallIds.filter((id) => !results.has(id));
74
+ return `${diagnosticId(assistant.visibleId ?? assistant.stableId)} (missing tool result: ${missing.slice(0, 3).map(diagnosticId).join(", ")}${missing.length > 3 ? ", …" : ""})`;
75
+ });
76
+ const boundaries = entries.flatMap((entry) => {
77
+ if (entry.index < closure.requestedStartIndex || entry.index > closure.requestedEndIndex ||
78
+ entry.passthrough || entry.origin === "dcp-control") return [];
79
+ const id = entry.blockId !== undefined ? `b${entry.blockId}` : entry.visibleId;
80
+ return id ? [{ id, messageIndex: entry.index }] : [];
81
+ });
82
+ const alternatives = protocolClosedBoundaryRuns(boundaries, entries);
83
+ const hints = alternatives.slice(0, 3).map((run) => `${run[0]!.id}..${run[run.length - 1]!.id}`);
84
+ return `Blocking group(s): ${details.join("; ")}${blockers.length > 3 ? "; …" : ""}. ` +
85
+ (hints.length > 0
86
+ ? `Protocol-safe alternatives inside this selection: ${hints.join(", ")}${alternatives.length > 3 ? ", …" : ""}. `
87
+ : "No protocol-closed subrange is available inside this selection. ") +
88
+ "Choose a closed range or message-mode candidates outside the blocked groups. " +
89
+ "Changing only an endId beyond a blocked group will not help; wait only if its tool is actually still running.";
90
+ }
@@ -5,7 +5,8 @@ import {
5
5
  estimateMessageTokens,
6
6
  } from "./pruner-metadata.js";
7
7
  import { stableMessageKeys } from "./pruner-message-ids.js";
8
- import { detectToolGroupSpans, findConversationIndexEntry } from "./conversation-index.js";
8
+ import { closeConversationRange, detectToolGroupSpans, findConversationIndexEntry } from "./conversation-index.js";
9
+ import { protocolClosedBoundaryRuns } from "./protocol-closed-ranges.js";
9
10
 
10
11
  interface CandidateBoundary {
11
12
  id: string;
@@ -120,58 +121,39 @@ function selectOldestSafePrefix(
120
121
  maxSafeMessageIndex: number,
121
122
  options?: CompressionCandidateSelectionOptions,
122
123
  ): CandidateBoundary[] | null {
123
- if (boundaries.length === 0) return null;
124
- if (!options || options.requiredSavingsTokens === undefined) {
125
- let full = boundaries.slice();
126
- while (full.length > 0 && full[0]!.isSystemReminder) full = full.slice(1);
127
- while (full.length > 0 && full[full.length - 1]!.isSystemReminder) full = full.slice(0, -1);
128
- if (full.length < Math.max(1, settings.minMessages)) return null;
129
- if (full.reduce((sum, boundary) => sum + boundary.tokenEstimate, 0) < settings.minTokens) return null;
130
- return full;
131
- }
132
- const requiredSavings = Math.max(0, Math.floor(options.requiredSavingsTokens));
124
+ const requiredSavings = Math.max(0, Math.floor(options?.requiredSavingsTokens ?? 0));
133
125
  const estimatorMargin = Math.max(0, Math.floor(options?.estimatorMarginTokens ?? 0));
134
126
  const targetSourceTokens = Math.max(settings.minTokens, requiredSavings + estimatorMargin);
135
127
  const minMessages = Math.max(1, settings.minMessages);
136
-
137
- let selectedEnd = -1;
138
- let selectedTokens = 0;
139
- for (let index = 0; index < boundaries.length; index++) {
140
- selectedTokens += boundaries[index]!.tokenEstimate;
141
- if (index + 1 >= minMessages && selectedTokens >= targetSourceTokens) {
142
- selectedEnd = index;
143
- break;
144
- }
145
- }
146
- if (selectedEnd < 0) selectedEnd = boundaries.length - 1;
147
-
148
- let selected = boundaries.slice(0, selectedEnd + 1);
149
- if (state.conversationIndexSnapshot.length > 0) {
150
- const groups = detectToolGroupSpans(state.conversationIndexSnapshot);
151
- const startMessageIndex = selected[0]!.messageIndex;
152
- let endMessageIndex = selected[selected.length - 1]!.messageIndex;
153
- let changed = true;
154
- while (changed) {
155
- changed = false;
156
- for (const group of groups) {
157
- const overlaps = group.startIndex <= endMessageIndex && group.endIndex >= startMessageIndex;
158
- if (!overlaps) continue;
159
- if (!group.complete) return null;
160
- if (group.endIndex > endMessageIndex) {
161
- if (group.endIndex > maxSafeMessageIndex) return null;
162
- endMessageIndex = group.endIndex;
163
- changed = true;
128
+ const eligible = boundaries.filter((boundary) => boundary.messageIndex <= maxSafeMessageIndex);
129
+ while (eligible[0]?.isSystemReminder) eligible.shift();
130
+ while (eligible[eligible.length - 1]?.isSystemReminder) eligible.pop();
131
+
132
+ for (const run of protocolClosedBoundaryRuns(eligible, state.conversationIndexSnapshot)) {
133
+ while (run[0]?.isSystemReminder) run.shift();
134
+ while (run[run.length - 1]?.isSystemReminder) run.pop();
135
+ if (run.length === 0) continue;
136
+ let selectedEnd = run.length - 1;
137
+ if (options?.requiredSavingsTokens !== undefined) {
138
+ let selectedTokens = 0;
139
+ for (let index = 0; index < run.length; index++) {
140
+ selectedTokens += run[index]!.tokenEstimate;
141
+ if (index + 1 >= minMessages && selectedTokens >= targetSourceTokens) {
142
+ selectedEnd = index;
143
+ break;
164
144
  }
165
145
  }
166
146
  }
167
- selected = boundaries.filter((boundary) => boundary.messageIndex <= endMessageIndex);
147
+ const closure = closeConversationRange(state.conversationIndexSnapshot, run[0]!.id, run[selectedEnd]!.id);
148
+ if (!closure || closure.incompleteToolGroup || closure.startIndex !== run[0]!.messageIndex ||
149
+ closure.endIndex > run[run.length - 1]!.messageIndex) continue;
150
+ const selected = run.filter((boundary) => boundary.messageIndex <= closure.endIndex);
151
+ if (selected[selected.length - 1]!.messageIndex !== closure.endIndex) continue;
152
+ if (selected.length < minMessages) continue;
153
+ if (selected.reduce((sum, boundary) => sum + boundary.tokenEstimate, 0) < settings.minTokens) continue;
154
+ return selected;
168
155
  }
169
-
170
- while (selected.length > 0 && selected[0]!.isSystemReminder) selected = selected.slice(1);
171
- while (selected.length > 0 && selected[selected.length - 1]!.isSystemReminder) selected = selected.slice(0, -1);
172
- if (selected.length < minMessages) return null;
173
- if (selected.reduce((sum, boundary) => sum + boundary.tokenEstimate, 0) < settings.minTokens) return null;
174
- return selected;
156
+ return null;
175
157
  }
176
158
 
177
159
  export function detectCompressionCandidate(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ui-extend",
3
- "version": "1.0.45",
3
+ "version": "1.0.47",
4
4
  "description": "Pix: a workspace-first terminal UI for Pi with tabs, readable tool activity, voice input, and bundled agent tools.",
5
5
  "private": false,
6
6
  "repository": {
@@ -13,8 +13,8 @@
13
13
  },
14
14
  "type": "module",
15
15
  "bin": {
16
- "pi-ui-extend": "./bin/pix.mjs",
17
- "pix": "./bin/pix.mjs"
16
+ "pi-ui-extend": "bin/pix.mjs",
17
+ "pix": "bin/pix.mjs"
18
18
  },
19
19
  "exports": {
20
20
  ".": {
@@ -48,19 +48,19 @@
48
48
  ],
49
49
  "scripts": {
50
50
  "predev": "npm run link:pix --silent",
51
- "dev": "mise exec node@24.16.0 -- tsx src/main.ts",
52
- "start": "mise exec node@24.16.0 -- tsx src/main.ts",
51
+ "dev": "tsx src/main.ts",
52
+ "start": "tsx src/main.ts",
53
53
  "prebuild:pix": "npm run generate-schemas --silent",
54
- "build:pix": "mise exec node@24.16.0 -- tsc -p tsconfig.json",
54
+ "build:pix": "tsc -p tsconfig.json",
55
55
  "prelink:pix": "npm run build:pix --silent",
56
- "link:pix": "mise exec node@24.16.0 -- npm link --silent",
57
- "watch:pix": "npm run link:pix --silent && mise exec node@24.16.0 -- node scripts/watch-pix.mjs",
58
- "watch:all": "mise exec node@24.16.0 -- node scripts/watch-all.mjs",
56
+ "link:pix": "npm link --silent",
57
+ "watch:pix": "npm run link:pix --silent && node scripts/watch-pix.mjs",
58
+ "watch:all": "node scripts/watch-all.mjs",
59
59
  "rebuild:all": "npm run build:desktop",
60
- "check": "mise exec node@24.16.0 -- npm run check:inner",
60
+ "check": "npm run check:inner",
61
61
  "precheck:inner": "npm run generate-schemas --silent",
62
62
  "check:inner": "npm run sync:sdk-pin:check --silent && tsc --noEmit && npm run test:inner",
63
- "test": "mise exec node@24.16.0 -- npm run test:inner",
63
+ "test": "npm run test:inner",
64
64
  "test:inner": "node --import tsx --test \"tests/**/*.test.ts\"",
65
65
  "test:tools-suite": "npm --prefix external/pi-tools-suite test",
66
66
  "test:dcp-session-sim": "npm --prefix external/pi-tools-suite run test:dcp-session-sim",
@@ -75,12 +75,12 @@
75
75
  "sync:pi-tools-suite:check": "node scripts/sync-pi-tools-suite.mjs --check",
76
76
  "sync:sdk-pin": "node scripts/sync-sdk-pin.mjs",
77
77
  "sync:sdk-pin:check": "node scripts/sync-sdk-pin.mjs --check",
78
- "test:coverage": "mise exec node@24.16.0 -- node --import tsx --test --experimental-test-coverage --test-coverage-include=src/**/*.ts --test-coverage-exclude=src/main.ts --test-coverage-lines=94 --test-coverage-branches=80 --test-coverage-functions=94 \"tests/**/*.test.ts\"",
79
- "update-sdk-references": "mise exec node@24.16.0 -- node .pi/skills/pi-sdk/scripts/update-references.mjs",
78
+ "test:coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-include=src/**/*.ts --test-coverage-exclude=src/main.ts --test-coverage-lines=94 --test-coverage-branches=80 --test-coverage-functions=94 \"tests/**/*.test.ts\"",
79
+ "update-sdk-references": "node .pi/skills/pi-sdk/scripts/update-references.mjs",
80
80
  "prepack": "npm run generate-schemas --silent",
81
81
  "pack:dry-run": "npm pack --dry-run",
82
- "smoke-test": "mise exec node@24.16.0 -- bash scripts/smoke-test-package.sh",
83
- "smoke-test:cli": "mise exec node@24.16.0 -- node scripts/smoke-test-package-cli.mjs",
82
+ "smoke-test": "bash scripts/smoke-test-package.sh",
83
+ "smoke-test:cli": "node scripts/smoke-test-package-cli.mjs",
84
84
  "release:check": "npm run check && npm run build:pix && npm run pack:dry-run",
85
85
  "publish-npm": "bash scripts/publish.sh",
86
86
  "generate-schemas": "tsx scripts/generate-schemas.ts",