@cydm/pie 1.0.47 → 1.0.48

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 +21 -1
  2. package/dist/cli.js +349 -131
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -218,7 +218,10 @@ Pie uses a hierarchical slash-command menu. The current built-in commands includ
218
218
  | `pie doctor [--json]` | Diagnose local config, model setup, writable paths, browser automation, and Unity bridge hints |
219
219
  | `pie models init` | Create a starter `~/.pie/models.json` |
220
220
  | `pie models validate` | Validate `~/.pie/models.json` |
221
- | `pie permissions` | Explain tool and shell safety defaults |
221
+ | `pie permissions` | Explain tool and shell safety defaults (`--json` returns the structured safety model) |
222
+ | `pie capabilities [--json]` | Report the command surface, flags, exit codes, and output schema for agents |
223
+ | `pie sessions list/read/rename/rewind/fork` | Inspect and manage stored sessions non-interactively (each supports `--json` and `--help`) |
224
+ | `pie extensions list/enable/disable` | Inspect and toggle extensions non-interactively |
222
225
  | `/sessions/compact` | Summarize history and create a compact checkpoint |
223
226
  | `/sessions/new` | Start a new session |
224
227
  | `/sessions/resume` | Resume a different session |
@@ -355,6 +358,23 @@ pie "summarize this file" --json-output
355
358
  pie "continue previous task" --session-id my-session
356
359
  ```
357
360
 
361
+ ### Machine-Readable Output (Agents And Hosts)
362
+
363
+ Every management command (`sessions`, `doctor`, `models`, `extensions`, `permissions`, `capabilities`) emits one compact JSON envelope when given `--json`:
364
+
365
+ ```json
366
+ {"v":1,"ok":true,"kind":"sessions.list","data":[...],"meta":{"count":10,"total":42,"nextCursor":"..."},"help":["..."]}
367
+ ```
368
+
369
+ - `v` is a constant output-schema generation marker; `kind` identifies the command.
370
+ - `meta` carries pagination (`nextCursor`) and aggregates; `help[]` lists concrete next steps when they exist and never duplicates large payloads.
371
+ - Errors are structured on stdout: `{"v":1,"ok":false,"kind":"...","error":{"code":"session_not_found","message":"...","hint":"..."}}`.
372
+ - Exit codes: `0` success, `1` business failure, `2` usage error. Unknown flags fail loudly; every subcommand supports `--help`.
373
+ - `sessions list --json` defaults to the current project (`--all` lists every project). Rows are minimal (`id`, `name`, `updatedAt`, `count`) with `--fields` and `--full` opt-ins; `sessions read` truncates content by default with the same opt-ins.
374
+ - JSONL protocol mode (`--json-events`) identifies entries by `entryId` only (the legacy `checkpointId` alias is gone) and caps large tool results and turn summaries by default; pass `--event-detail full` for untruncated payloads. Truncation always carries `truncated`/`fullLength` markers.
375
+
376
+ Run `pie capabilities --json` to discover the full command surface, flags, and exit-code contract programmatically.
377
+
358
378
  ## Terminal Notes
359
379
 
360
380
  For the best multiline editing experience, use a terminal with good modified-key support, such as:
package/dist/cli.js CHANGED
@@ -51654,6 +51654,33 @@ var SelectList = class {
51654
51654
  }
51655
51655
  invalidate() {
51656
51656
  }
51657
+ /**
51658
+ * Lay out one item row: label (left column) + description (right column).
51659
+ *
51660
+ * The label column is sized from the actual labels and the available
51661
+ * width. The previous fixed 30-column cap truncated long commands (e.g.
51662
+ * /skills/skill:terminal-pty-testing) even on wide terminals.
51663
+ */
51664
+ renderLabelParts(prefix, displayValue, descriptionSingleLine, useDescriptionColumn, labelColumnWidth, width) {
51665
+ const prefixWidth = visibleWidth(prefix);
51666
+ if (useDescriptionColumn && descriptionSingleLine) {
51667
+ const truncatedValue = truncateToWidth(displayValue, labelColumnWidth, "");
51668
+ const valueWidth = visibleWidth(truncatedValue);
51669
+ const spacing = " ".repeat(Math.max(1, labelColumnWidth - valueWidth + 2));
51670
+ const descriptionStart = prefixWidth + valueWidth + spacing.length;
51671
+ const remainingWidth = width - descriptionStart - 2;
51672
+ if (remainingWidth > 10) {
51673
+ return {
51674
+ value: truncatedValue,
51675
+ spacing,
51676
+ desc: truncateToWidth(descriptionSingleLine, remainingWidth, "")
51677
+ };
51678
+ }
51679
+ return { value: truncateToWidth(displayValue, width - prefixWidth - 2, ""), spacing: "", desc: "" };
51680
+ }
51681
+ const maxWidth = width - prefixWidth - 2;
51682
+ return { value: truncateToWidth(displayValue, maxWidth, ""), spacing: "", desc: "" };
51683
+ }
51657
51684
  render(width) {
51658
51685
  const lines = [];
51659
51686
  if (this.filteredItems.length === 0) {
@@ -51661,54 +51688,35 @@ var SelectList = class {
51661
51688
  return lines;
51662
51689
  }
51663
51690
  const { startIndex, endIndex } = this.getVisibleRange();
51691
+ const useDescriptionColumn = this.filteredItems.some((item) => item.description) && width > 40;
51692
+ let labelColumnWidth = 0;
51693
+ if (useDescriptionColumn) {
51694
+ const maxLabelWidth = Math.max(
51695
+ ...this.filteredItems.map((item) => visibleWidth(item.label || item.value))
51696
+ );
51697
+ labelColumnWidth = Math.max(8, Math.min(maxLabelWidth, Math.floor(width * 0.45)));
51698
+ }
51664
51699
  for (let i = startIndex; i < endIndex; i++) {
51665
51700
  const item = this.filteredItems[i];
51666
51701
  if (!item) continue;
51667
51702
  const isSelected = i === this.selectedIndex;
51668
51703
  const descriptionSingleLine = item.description ? normalizeToSingleLine(item.description) : void 0;
51704
+ const displayValue = item.label || item.value;
51669
51705
  let line = "";
51670
51706
  if (isSelected) {
51671
- const prefixWidth = 2;
51672
- const displayValue = item.label || item.value;
51673
- if (descriptionSingleLine && width > 40) {
51674
- const maxValueWidth = Math.min(30, width - prefixWidth - 4);
51675
- const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
51676
- const valueWidth = visibleWidth(truncatedValue);
51677
- const spacing = " ".repeat(Math.max(1, 32 - valueWidth));
51678
- const descriptionStart = prefixWidth + valueWidth + spacing.length;
51679
- const remainingWidth = width - descriptionStart - 2;
51680
- if (remainingWidth > 10) {
51681
- const truncatedDesc = truncateToWidth(descriptionSingleLine, remainingWidth, "");
51682
- line = truncateToWidth(this.theme.selectedText(`\u2192 ${truncatedValue}${spacing}${truncatedDesc}`), width, "");
51683
- } else {
51684
- const maxWidth = width - prefixWidth - 2;
51685
- line = truncateToWidth(this.theme.selectedText(`\u2192 ${truncateToWidth(displayValue, maxWidth, "")}`), width, "");
51686
- }
51687
- } else {
51688
- const maxWidth = width - prefixWidth - 2;
51689
- line = truncateToWidth(this.theme.selectedText(`\u2192 ${truncateToWidth(displayValue, maxWidth, "")}`), width, "");
51690
- }
51707
+ const parts = this.renderLabelParts("\u2192 ", displayValue, descriptionSingleLine, useDescriptionColumn, labelColumnWidth, width);
51708
+ line = truncateToWidth(
51709
+ this.theme.selectedText(`\u2192 ${parts.value}${parts.spacing}${parts.desc}`),
51710
+ width,
51711
+ ""
51712
+ );
51691
51713
  } else {
51692
- const displayValue = item.label || item.value;
51693
- const prefix = " ";
51694
- if (descriptionSingleLine && width > 40) {
51695
- const maxValueWidth = Math.min(30, width - prefix.length - 4);
51696
- const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
51697
- const valueWidth = visibleWidth(truncatedValue);
51698
- const spacing = " ".repeat(Math.max(1, 32 - valueWidth));
51699
- const descriptionStart = prefix.length + valueWidth + spacing.length;
51700
- const remainingWidth = width - descriptionStart - 2;
51701
- if (remainingWidth > 10) {
51702
- const truncatedDesc = truncateToWidth(descriptionSingleLine, remainingWidth, "");
51703
- const descText = this.theme.description(spacing + truncatedDesc);
51704
- line = truncateToWidth(prefix + truncatedValue + descText, width, "");
51705
- } else {
51706
- const maxWidth = width - prefix.length - 2;
51707
- line = truncateToWidth(prefix + truncateToWidth(displayValue, maxWidth, ""), width, "");
51708
- }
51714
+ const parts = this.renderLabelParts(" ", displayValue, descriptionSingleLine, useDescriptionColumn, labelColumnWidth, width);
51715
+ if (parts.desc) {
51716
+ const descText = this.theme.description(parts.spacing + parts.desc);
51717
+ line = truncateToWidth(" " + parts.value + descText, width, "");
51709
51718
  } else {
51710
- const maxWidth = width - prefix.length - 2;
51711
- line = truncateToWidth(prefix + truncateToWidth(displayValue, maxWidth, ""), width, "");
51719
+ line = truncateToWidth(" " + parts.value, width, "");
51712
51720
  }
51713
51721
  }
51714
51722
  lines.push(truncateToWidth(line, width, ""));
@@ -51847,6 +51855,13 @@ var Editor = class {
51847
51855
  autocompleteState = null;
51848
51856
  autocompletePrefix = "";
51849
51857
  autocompleteMaxVisible = 5;
51858
+ /**
51859
+ * Whether the user moved the autocomplete selection (arrows/paging).
51860
+ * A bare "/" + Enter still submits the slash itself (root command menu),
51861
+ * but once the user has actively highlighted an item, Enter must accept
51862
+ * that selection instead of re-opening the same menu.
51863
+ */
51864
+ autocompleteListTouched = false;
51850
51865
  // Paste tracking for large pastes
51851
51866
  pastes = /* @__PURE__ */ new Map();
51852
51867
  pasteCounter = 0;
@@ -52083,6 +52098,12 @@ var Editor = class {
52083
52098
  return;
52084
52099
  }
52085
52100
  if (kb.matches(data, "selectUp") || kb.matches(data, "selectDown")) {
52101
+ this.autocompleteListTouched = true;
52102
+ this.autocompleteList.handleInput(data);
52103
+ return;
52104
+ }
52105
+ if (kb.matches(data, "cursorLeft") || kb.matches(data, "cursorRight") || kb.matches(data, "pageUp") || kb.matches(data, "pageDown")) {
52106
+ this.autocompleteListTouched = true;
52086
52107
  this.autocompleteList.handleInput(data);
52087
52108
  return;
52088
52109
  }
@@ -52108,7 +52129,7 @@ var Editor = class {
52108
52129
  }
52109
52130
  if (kb.matches(data, "selectConfirm")) {
52110
52131
  const currentLine = this.state.lines[this.state.cursorLine] || "";
52111
- const isBareSlashSubmit = this.state.lines.length === 1 && currentLine === "/" && this.state.cursorCol === 1;
52132
+ const isBareSlashSubmit = !this.autocompleteListTouched && this.state.lines.length === 1 && currentLine === "/" && this.state.cursorCol === 1;
52112
52133
  if (isBareSlashSubmit) {
52113
52134
  this.cancelAutocomplete();
52114
52135
  } else {
@@ -53128,12 +53149,29 @@ var Editor = class {
53128
53149
  );
53129
53150
  if (suggestions && suggestions.items.length > 0) {
53130
53151
  this.autocompletePrefix = suggestions.prefix;
53131
- this.autocompleteList = new SelectList(suggestions.items, this.autocompleteMaxVisible, this.theme.selectList);
53152
+ this.autocompleteListTouched = false;
53153
+ this.autocompleteList = new SelectList(
53154
+ suggestions.items,
53155
+ this.computeAutocompleteMaxVisible(suggestions.items.length),
53156
+ this.theme.selectList
53157
+ );
53132
53158
  this.autocompleteState = "regular";
53133
53159
  } else {
53134
53160
  this.cancelAutocomplete();
53135
53161
  }
53136
53162
  }
53163
+ /**
53164
+ * Visible autocomplete rows, scaled with terminal height so tall screens
53165
+ * show the full command list instead of paginating five rows at a time.
53166
+ * Floor: the configured max (default 5). Ceiling: ~45% of rows, capped at
53167
+ * 20 so a huge terminal never dedicates half the screen to suggestions.
53168
+ */
53169
+ computeAutocompleteMaxVisible(itemCount) {
53170
+ const rows = this.tui.terminal.rows;
53171
+ const heightScaled = Number.isFinite(rows) ? Math.floor(rows * 0.45) : this.autocompleteMaxVisible;
53172
+ const desired = Math.max(this.autocompleteMaxVisible, Math.min(heightScaled, 20));
53173
+ return Math.max(1, Math.min(desired, Math.max(itemCount, 1)));
53174
+ }
53137
53175
  handleTabCompletion() {
53138
53176
  if (!this.autocompleteProvider) return;
53139
53177
  const currentLine = this.state.lines[this.state.cursorLine] || "";
@@ -53183,7 +53221,12 @@ var Editor = class {
53183
53221
  return;
53184
53222
  }
53185
53223
  this.autocompletePrefix = suggestions.prefix;
53186
- this.autocompleteList = new SelectList(suggestions.items, this.autocompleteMaxVisible, this.theme.selectList);
53224
+ this.autocompleteListTouched = false;
53225
+ this.autocompleteList = new SelectList(
53226
+ suggestions.items,
53227
+ this.computeAutocompleteMaxVisible(suggestions.items.length),
53228
+ this.theme.selectList
53229
+ );
53187
53230
  this.autocompleteState = "force";
53188
53231
  } else {
53189
53232
  this.cancelAutocomplete();
@@ -53193,6 +53236,7 @@ var Editor = class {
53193
53236
  this.autocompleteState = null;
53194
53237
  this.autocompleteList = void 0;
53195
53238
  this.autocompletePrefix = "";
53239
+ this.autocompleteListTouched = false;
53196
53240
  }
53197
53241
  isShowingAutocomplete() {
53198
53242
  return this.autocompleteState !== null;
@@ -53210,7 +53254,11 @@ var Editor = class {
53210
53254
  );
53211
53255
  if (suggestions && suggestions.items.length > 0) {
53212
53256
  this.autocompletePrefix = suggestions.prefix;
53213
- this.autocompleteList = new SelectList(suggestions.items, this.autocompleteMaxVisible, this.theme.selectList);
53257
+ this.autocompleteList = new SelectList(
53258
+ suggestions.items,
53259
+ this.computeAutocompleteMaxVisible(suggestions.items.length),
53260
+ this.theme.selectList
53261
+ );
53214
53262
  } else {
53215
53263
  this.cancelAutocomplete();
53216
53264
  }
@@ -55394,6 +55442,17 @@ var SettingsList = class {
55394
55442
  item.currentValue = newValue;
55395
55443
  }
55396
55444
  }
55445
+ /**
55446
+ * Move the selection to an item by id (respects the active filter).
55447
+ * Used when a command targets a specific row (e.g. /settings/theme).
55448
+ */
55449
+ focusItemById(id) {
55450
+ const displayItems = this.searchEnabled ? this.filteredItems : this.items;
55451
+ const index = displayItems.findIndex((item) => item.id === id);
55452
+ if (index >= 0) {
55453
+ this.selectedIndex = index;
55454
+ }
55455
+ }
55397
55456
  invalidate() {
55398
55457
  this.submenuComponent?.invalidate?.();
55399
55458
  }
@@ -62341,6 +62400,9 @@ var SettingsSelectorComponent = class extends Container {
62341
62400
  { enableSearch: true }
62342
62401
  );
62343
62402
  this.addChild(this.settingsList);
62403
+ if (config.focusItem) {
62404
+ this.settingsList.focusItemById(config.focusItem);
62405
+ }
62344
62406
  }
62345
62407
  getSettingsList() {
62346
62408
  return this.settingsList;
@@ -62447,36 +62509,6 @@ function selectRuntimeExtensionWinners(infos) {
62447
62509
  }
62448
62510
  return reversed.reverse();
62449
62511
  }
62450
- var ExtensionStateSubmenu = class extends Container {
62451
- selectList;
62452
- constructor(identity, currentValue, scope, onSelect, onCommitted, onCancel) {
62453
- super();
62454
- this.addChild(new Text(theme.bold(theme.fg("accent", identity)), 0, 0));
62455
- this.addChild(new Spacer(1));
62456
- const settingsPath = scope === "project" ? "./.pie/settings.json" : "~/.pie/settings.json";
62457
- this.addChild(new Text(theme.fg("muted", `Save an override in ${settingsPath}`), 0, 0));
62458
- this.addChild(new Spacer(1));
62459
- const options = [
62460
- { value: "on", label: "On", description: "Enable on next start" },
62461
- { value: "off", label: "Off", description: "Disable on next start" }
62462
- ];
62463
- this.selectList = new SelectList(options, options.length, getSelectListTheme());
62464
- this.selectList.setSelectedIndex(currentValue === "off" ? 1 : 0);
62465
- this.selectList.onSelect = (item) => {
62466
- const effective = onSelect(item.value === "on");
62467
- if (effective !== void 0) {
62468
- onCommitted(effective);
62469
- }
62470
- };
62471
- this.selectList.onCancel = onCancel;
62472
- this.addChild(this.selectList);
62473
- this.addChild(new Spacer(1));
62474
- this.addChild(new Text(theme.fg("dim", " Enter to select \xB7 Esc to go back"), 0, 0));
62475
- }
62476
- handleInput(data) {
62477
- this.selectList.handleInput(data);
62478
- }
62479
- };
62480
62512
  function unidentifiedLabel(info) {
62481
62513
  const parentName = path23.basename(path23.dirname(info.entryPath));
62482
62514
  const fileName = path23.basename(info.entryPath);
@@ -62497,37 +62529,41 @@ var ExtensionEnablementSelectorComponent = class extends Container {
62497
62529
  0
62498
62530
  ));
62499
62531
  this.addChild(new Spacer(1));
62532
+ const identityById = /* @__PURE__ */ new Map();
62533
+ const lastPersistedValue = /* @__PURE__ */ new Map();
62500
62534
  const items = selectRuntimeExtensionWinners(infos).map((info, index) => {
62501
62535
  const identity = info.identity;
62502
62536
  const descriptionParts = [`Source: ${info.source}`];
62503
62537
  if (info.description) descriptionParts.push(info.description);
62504
62538
  if (!identity) descriptionParts.push("No stable identity; this entry is read-only.");
62539
+ const id = identity ?? `unidentified:${index}`;
62540
+ if (identity) {
62541
+ identityById.set(id, identity);
62542
+ lastPersistedValue.set(id, info.enabled ? "on" : "off");
62543
+ }
62505
62544
  const item = {
62506
- id: `extension:${index}`,
62545
+ id,
62507
62546
  label: identity ?? unidentifiedLabel(info),
62508
62547
  description: descriptionParts.join(" \xB7 "),
62509
62548
  currentValue: info.enabled ? "on" : "off"
62510
62549
  };
62511
- if (identity) {
62512
- item.submenu = (currentValue, done) => {
62513
- const submenu = new ExtensionStateSubmenu(
62514
- identity,
62515
- currentValue,
62516
- scope,
62517
- (enabled) => callbacks.onToggle(identity, enabled),
62518
- (effectiveEnabled) => done(effectiveEnabled ? "on" : "off"),
62519
- () => done()
62520
- );
62521
- return submenu;
62522
- };
62523
- }
62550
+ if (identity) item.values = ["on", "off"];
62524
62551
  return item;
62525
62552
  });
62526
62553
  this.settingsList = new SettingsList(
62527
62554
  items,
62528
62555
  10,
62529
62556
  getSettingsListTheme(),
62530
- () => {
62557
+ (id, newValue) => {
62558
+ const identity = identityById.get(id);
62559
+ if (!identity) return;
62560
+ const previous = lastPersistedValue.get(id) ?? "on";
62561
+ const accepted = callbacks.onToggle(identity, newValue === "on");
62562
+ if (accepted === false) {
62563
+ this.settingsList.updateValue(id, previous);
62564
+ return;
62565
+ }
62566
+ lastPersistedValue.set(id, newValue);
62531
62567
  },
62532
62568
  callbacks.onCancel,
62533
62569
  { enableSearch: true }
@@ -62587,9 +62623,9 @@ function createExtensionSelector(host, scope, onCancel) {
62587
62623
  return new ExtensionEnablementSelectorComponent(infos, scope, {
62588
62624
  onToggle: (identity, enabled) => {
62589
62625
  const outcome = trySetExtensionEnabled(host, identity, enabled, scope);
62590
- if (!outcome) return void 0;
62626
+ if (!outcome) return false;
62591
62627
  host.showStatus(outcome.message);
62592
- return outcome.effectiveEnabled;
62628
+ return true;
62593
62629
  },
62594
62630
  onCancel
62595
62631
  });
@@ -62679,16 +62715,21 @@ var BUILTIN_SLASH_COMMANDS = [
62679
62715
  { name: "debugs/context", description: "Append a context summary to ~/.pie/logs/pie-cli.log" },
62680
62716
  { name: "debugs/package", description: "Append a package summary to ~/.pie/logs/pie-cli.log" },
62681
62717
  { name: "settings/quit", description: "Exit Pie" },
62682
- { name: "skills", description: "Open skills menu" },
62683
- { name: "skills/list", description: "List available skills" },
62718
+ { name: "skills", description: "Manage skills: list, toggle enable/disable, invoke" },
62719
+ { name: "skills/list", description: "List available skills (plain text)" },
62720
+ { name: "skills/create", description: "Create a new skill from a template" },
62684
62721
  { name: "skills/enable", description: "Enable a skill by name" },
62685
62722
  { name: "skills/disable", description: "Disable a skill by name" },
62686
62723
  { name: "skills/reload", description: "Reload skills from disk" },
62687
62724
  { name: "skills/use", description: "Use a skill" },
62688
62725
  { name: "tools", description: "List tools and manage enable/disable toggles" },
62726
+ { name: "tools/list", description: "List runtime tools and their state (plain text)" },
62689
62727
  { name: "tools/enable", description: "Enable a tool by name" },
62690
62728
  { name: "tools/disable", description: "Disable a tool by name" },
62691
- { name: "tools/subagent", description: "Spawn subagents in parallel to handle tasks" }
62729
+ { name: "tools/subagent", description: "Spawn subagents in parallel to handle tasks" },
62730
+ { name: "files/upload", description: "Attach a file to the conversation" },
62731
+ { name: "files/upload-pic", description: "Attach an image to the conversation" },
62732
+ { name: "files/upload-video", description: "Attach a video to the conversation" }
62692
62733
  ];
62693
62734
 
62694
62735
  // src/commands/interactive-debug-commands.ts
@@ -65261,6 +65302,81 @@ function errorMessage2(error) {
65261
65302
  return error instanceof Error ? error.message : String(error);
65262
65303
  }
65263
65304
  function handleSkillsCommand(host) {
65305
+ host.showSelector((done) => {
65306
+ const settingsManager = host.options.settingsManager;
65307
+ const discovered = listDiscoveredSkills(host);
65308
+ const items = discovered.map((skill) => ({
65309
+ id: `skill:${skill.name}`,
65310
+ label: skill.name,
65311
+ description: `${skill.description} \xB7 Source: ${skill.source ?? "unknown"}`,
65312
+ currentValue: settingsManager.isSkillEnabled(skill.name) ? "on" : "off",
65313
+ values: ["on", "off"]
65314
+ }));
65315
+ const settingsList = new SettingsList(
65316
+ items,
65317
+ 12,
65318
+ {
65319
+ label: (text, selected) => selected ? theme.fg("accent", text) : theme.fg("text", text),
65320
+ value: (text, selected) => selected ? theme.fg("accent", text) : theme.fg("accent", text),
65321
+ description: (text) => theme.fg("muted", text),
65322
+ cursor: "> ",
65323
+ hint: (text) => theme.fg("dim", text)
65324
+ },
65325
+ (id, newValue) => {
65326
+ const name = id.slice("skill:".length);
65327
+ const enabled = newValue === "on";
65328
+ try {
65329
+ settingsManager.setSkillEnabled(name, enabled);
65330
+ } catch (error) {
65331
+ host.showError(`Failed to update skill "${name}": ${errorMessage2(error)}`);
65332
+ return;
65333
+ }
65334
+ applyReloadedSkills(host, discoverSkills(host));
65335
+ host.showStatus(`Skill "${name}" ${enabled ? "enabled" : "disabled"} (reloaded).`);
65336
+ },
65337
+ () => done(),
65338
+ { enableSearch: true }
65339
+ );
65340
+ const component = {
65341
+ render: (width) => [
65342
+ theme.bold(theme.fg("accent", "Skills")),
65343
+ "",
65344
+ theme.fg("muted", "Enter/Space toggles \xB7 changes apply immediately \xB7 Esc to close"),
65345
+ theme.fg("dim", `Usage: /skills/skill:<name> <your request> (${discovered.length} discovered)`),
65346
+ "",
65347
+ ...settingsList.render(width)
65348
+ ],
65349
+ handleInput: (data) => settingsList.handleInput(data),
65350
+ invalidate: () => settingsList.invalidate()
65351
+ };
65352
+ return { component, focus: settingsList };
65353
+ });
65354
+ }
65355
+ function discoverSkills(host) {
65356
+ const builtinDir = path25.join(getBuiltinDir(), "skills");
65357
+ const userDir = path25.join(os7.homedir(), ".pie", "skills");
65358
+ const projectDir = path25.join(process.cwd(), ".pie", "skills");
65359
+ return loadSkills({ dirs: buildSkillDirs({ builtinDir, userDir, projectDir, cwd: process.cwd() }) });
65360
+ }
65361
+ function listDiscoveredSkills(host) {
65362
+ return discoverSkills(host).skills;
65363
+ }
65364
+ function applyReloadedSkills(host, result) {
65365
+ host.skills = result.skills.filter((skill) => host.options.settingsManager.isSkillEnabled(skill.name));
65366
+ host.skillsSection = formatSkillsForPrompt(host.skills);
65367
+ host.applyRuntimeToolMode(host.allTools.length > 0 ? host.allTools : host.agent.state.tools);
65368
+ host.updateSystemPromptTools();
65369
+ for (const skill of host.skills) {
65370
+ const skillPath = ["skills", `skill:${skill.name}`];
65371
+ if (!commandTree.find(skillPath)) {
65372
+ commandTree.register(skillPath, {
65373
+ description: skill.description,
65374
+ handler: (_2, args) => host.handleSkillInvoke(skill.name, args || "")
65375
+ });
65376
+ }
65377
+ }
65378
+ }
65379
+ function handleSkillsListCommand(host) {
65264
65380
  host.chatContainer.addChild(new Spacer(1));
65265
65381
  host.chatContainer.addChild(new Text(theme.bold("Available Skills"), 1, 0));
65266
65382
  if (host.skills.length === 0) {
@@ -65417,7 +65533,12 @@ async function handleSkillToggleCommand(host, args, enabled) {
65417
65533
  host.ui.requestRender();
65418
65534
  }
65419
65535
  async function handleSkillsCreate(host, args) {
65420
- console.error(`[SkillsCreate] Called with args: "${args}"`);
65536
+ const debugLog2 = (message) => {
65537
+ if (process.env.PIE_VERBOSE) {
65538
+ console.error(`[SkillsCreate] ${message}`);
65539
+ }
65540
+ };
65541
+ debugLog2(`Called with args: "${args}"`);
65421
65542
  try {
65422
65543
  const trimmed = args.trim();
65423
65544
  if (!trimmed) {
@@ -65448,7 +65569,7 @@ async function handleSkillsCreate(host, args) {
65448
65569
  description = descPart.replace(/^["']|["']$/g, "");
65449
65570
  }
65450
65571
  }
65451
- console.error(`[SkillsCreate] Parsed name: "${name}", description: "${description}"`);
65572
+ debugLog2(`Parsed name: "${name}", description: "${description}"`);
65452
65573
  if (!name) {
65453
65574
  host.showSkillCreatePrompt();
65454
65575
  return;
@@ -65464,7 +65585,7 @@ async function handleSkillsCreate(host, args) {
65464
65585
  const skillsDir = path25.join(os7.homedir(), ".pie", "skills");
65465
65586
  const skillDir = path25.join(skillsDir, name);
65466
65587
  const skillFile = path25.join(skillDir, "SKILL.md");
65467
- console.error(`[SkillsCreate] Creating at: ${skillFile}`);
65588
+ debugLog2(`Creating at: ${skillFile}`);
65468
65589
  if (fs23.existsSync(skillDir)) {
65469
65590
  host.showError(`Skill "${name}" already exists at ${skillDir}`);
65470
65591
  return;
@@ -65472,7 +65593,7 @@ async function handleSkillsCreate(host, args) {
65472
65593
  fs23.mkdirSync(skillDir, { recursive: true });
65473
65594
  const template = createSkillTemplate(name, description);
65474
65595
  fs23.writeFileSync(skillFile, template, "utf-8");
65475
- console.error(`[SkillsCreate] Successfully created skill`);
65596
+ debugLog2("Successfully created skill");
65476
65597
  host.chatContainer.addChild(new Spacer(1));
65477
65598
  host.chatContainer.addChild(new Text(theme.fg("accent", `\u2713 Created skill: ${name}`), 1, 0));
65478
65599
  host.chatContainer.addChild(new Text(theme.fg("dim", ` Location: ${skillFile}`), 1, 0));
@@ -65481,7 +65602,7 @@ async function handleSkillsCreate(host, args) {
65481
65602
  host.chatContainer.addChild(new Spacer(1));
65482
65603
  host.ui.requestRender();
65483
65604
  } catch (err) {
65484
- console.error(`[SkillsCreate] Error: ${errorMessage2(err)}`);
65605
+ debugLog2(`Error: ${errorMessage2(err)}`);
65485
65606
  host.showError(`Failed to create skill: ${errorMessage2(err)}`);
65486
65607
  }
65487
65608
  }
@@ -65556,6 +65677,67 @@ function collectKnownToolNames(host) {
65556
65677
  const disabled = disabledNames.filter((name) => !liveNames.includes(name));
65557
65678
  return { active: [...liveNames].sort(), disabled: [...disabled].sort() };
65558
65679
  }
65680
+ function showToolsManager(host) {
65681
+ host.showSelector((done) => {
65682
+ const { active, disabled } = collectKnownToolNames(host);
65683
+ const settingsManager = host.options.settingsManager;
65684
+ const items = [
65685
+ ...active.map((name) => ({
65686
+ id: `tool:${name}`,
65687
+ label: name,
65688
+ description: "Runtime agent tool",
65689
+ currentValue: settingsManager.isToolEnabled(name) ? "on" : "off",
65690
+ values: ["on", "off"]
65691
+ })),
65692
+ ...disabled.map((name) => ({
65693
+ id: `tool:${name}`,
65694
+ label: name,
65695
+ description: "Not loaded this session (start with it enabled to use it)",
65696
+ currentValue: "off",
65697
+ values: ["on", "off"]
65698
+ }))
65699
+ ];
65700
+ const settingsList = new SettingsList(
65701
+ items,
65702
+ 14,
65703
+ getSettingsListTheme(),
65704
+ (_id, newValue) => {
65705
+ const name = _id.slice("tool:".length);
65706
+ const enabled = newValue === "on";
65707
+ try {
65708
+ settingsManager.setToolEnabled(name, enabled);
65709
+ } catch (error) {
65710
+ host.showError(`Failed to update tool "${name}": ${errorMessage3(error)}`);
65711
+ return;
65712
+ }
65713
+ host.applyRuntimeToolMode();
65714
+ host.showStatus(
65715
+ enabled ? `Tool "${name}" enabled (active now).` : `Tool "${name}" disabled (removed from this session).`
65716
+ );
65717
+ },
65718
+ () => done(),
65719
+ { enableSearch: true }
65720
+ );
65721
+ const component = {
65722
+ render: (width) => [
65723
+ ...renderHeader(width),
65724
+ ...settingsList.render(width)
65725
+ ],
65726
+ handleInput: (data) => settingsList.handleInput(data),
65727
+ invalidate: () => settingsList.invalidate()
65728
+ };
65729
+ return { component, focus: settingsList };
65730
+ });
65731
+ }
65732
+ function renderHeader(width) {
65733
+ const lines = [];
65734
+ lines.push(theme.bold(theme.fg("accent", "Tools")));
65735
+ lines.push("");
65736
+ lines.push(theme.fg("muted", "Enter/Space toggles \xB7 changes apply immediately \xB7 Esc to close"));
65737
+ const hint = "Usage: /tools/enable <name> | /tools/disable <name>";
65738
+ if (width > 20) lines.push(theme.fg("dim", hint));
65739
+ return lines;
65740
+ }
65559
65741
  function handleToolsCommand(host, args) {
65560
65742
  const parts = args.trim().split(/\s+/).filter(Boolean);
65561
65743
  const subcommand = parts[0];
@@ -65565,8 +65747,8 @@ function handleToolsCommand(host, args) {
65565
65747
  host.showError(`Usage: /tools ${subcommand} <tool-name>`);
65566
65748
  return;
65567
65749
  }
65568
- const { active: active2, disabled: disabled2 } = collectKnownToolNames(host);
65569
- if (!active2.includes(name) && !disabled2.includes(name)) {
65750
+ const { active, disabled } = collectKnownToolNames(host);
65751
+ if (!active.includes(name) && !disabled.includes(name)) {
65570
65752
  host.showError(`Tool not found: ${name}. Use /tools to list tool names.`);
65571
65753
  return;
65572
65754
  }
@@ -65577,38 +65759,42 @@ function handleToolsCommand(host, args) {
65577
65759
  host.showError(`Failed to update tool "${name}": ${errorMessage3(error)}`);
65578
65760
  return;
65579
65761
  }
65762
+ host.applyRuntimeToolMode();
65580
65763
  host.chatContainer.addChild(new Spacer(1));
65581
65764
  host.chatContainer.addChild(new Text(
65582
- theme.fg("accent", `Tool "${name}" ${enabled ? "enabled" : "disabled"} in ~/.pie/settings.json. Takes effect on next start.`),
65765
+ theme.fg("accent", `Tool "${name}" ${enabled ? "enabled" : "disabled"} in ~/.pie/settings.json (applied now).`),
65583
65766
  1,
65584
65767
  0
65585
65768
  ));
65586
65769
  host.ui.requestRender();
65587
65770
  return;
65588
65771
  }
65589
- if (subcommand) {
65590
- host.showError("Usage: /tools [enable|disable <tool-name>]");
65772
+ if (subcommand === "list") {
65773
+ const { active, disabled } = collectKnownToolNames(host);
65774
+ host.chatContainer.addChild(new Spacer(1));
65775
+ host.chatContainer.addChild(new Text(theme.bold("Tools"), 1, 0));
65776
+ if (active.length === 0 && disabled.length === 0) {
65777
+ host.chatContainer.addChild(new Text(theme.fg("dim", " No tools available."), 1, 0));
65778
+ }
65779
+ for (const name of active) {
65780
+ const state = host.options.settingsManager.isToolEnabled(name) ? "enabled" : "disabled (removed from this session)";
65781
+ host.chatContainer.addChild(new Text(theme.fg("dim", ` \u2022 ${name}: ${state}`), 1, 0));
65782
+ }
65783
+ for (const name of disabled) {
65784
+ host.chatContainer.addChild(new Text(theme.fg("dim", ` \u2022 ${name}: disabled (/tools/enable ${name})`), 1, 0));
65785
+ }
65786
+ host.ui.requestRender();
65591
65787
  return;
65592
65788
  }
65593
- const { active, disabled } = collectKnownToolNames(host);
65594
- host.chatContainer.addChild(new Spacer(1));
65595
- host.chatContainer.addChild(new Text(theme.bold("Tools"), 1, 0));
65596
- if (active.length === 0 && disabled.length === 0) {
65597
- host.chatContainer.addChild(new Text(theme.fg("dim", " No tools available."), 1, 0));
65598
- }
65599
- for (const name of active) {
65600
- host.chatContainer.addChild(new Text(theme.fg("dim", ` \u2022 ${name}: enabled`), 1, 0));
65601
- }
65602
- for (const name of disabled) {
65603
- host.chatContainer.addChild(new Text(theme.fg("dim", ` \u2022 ${name}: disabled (/tools/enable ${name})`), 1, 0));
65789
+ if (subcommand) {
65790
+ host.showError("Usage: /tools [list | enable|disable <tool-name>]");
65791
+ return;
65604
65792
  }
65605
- host.chatContainer.addChild(new Spacer(1));
65606
- host.chatContainer.addChild(new Text(theme.fg("dim", "Usage: /tools/enable <name> | /tools/disable <name> (takes effect on next start)"), 1, 0));
65607
- host.ui.requestRender();
65793
+ showToolsManager(host);
65608
65794
  }
65609
65795
 
65610
65796
  // src/commands/interactive-command-handlers.ts
65611
- function showSettingsSelector(host) {
65797
+ function showSettingsSelector(host, focusItem) {
65612
65798
  host.showSelector((done) => {
65613
65799
  const currentModelId = host.options.model?.id || "unknown";
65614
65800
  const currentProvider = host.options.model?.provider || "unknown";
@@ -65624,7 +65810,8 @@ function showSettingsSelector(host) {
65624
65810
  yoloMode: host.yoloMode,
65625
65811
  calmMode: host.calmMode,
65626
65812
  notificationsBell: host.options.settingsManager.getBellNotificationsEnabled(),
65627
- notificationsDesktop: host.options.settingsManager.getDesktopNotificationsEnabled()
65813
+ notificationsDesktop: host.options.settingsManager.getDesktopNotificationsEnabled(),
65814
+ focusItem
65628
65815
  },
65629
65816
  {
65630
65817
  onThinkingLevelChange: (level) => {
@@ -66012,7 +66199,19 @@ async function handleClearCommand(host) {
66012
66199
  host.syncSessionModelMetadata();
66013
66200
  host.planModeEnabled = false;
66014
66201
  host.pendingPlanModeTransition = null;
66015
- host.footer.setState({ planMode: false });
66202
+ const contextUsage = host.calculateContextUsage();
66203
+ const contextWindow = host.options.model?.contextWindow ?? 0;
66204
+ host.footer.setState({
66205
+ planMode: false,
66206
+ status: "ready",
66207
+ inputTokens: contextUsage.input,
66208
+ outputTokens: contextUsage.output,
66209
+ cacheRead: contextUsage.cacheRead,
66210
+ cacheWrite: contextUsage.cacheWrite,
66211
+ cost: contextUsage.cost,
66212
+ contextPercent: contextWindow > 0 && contextUsage.tokens !== null ? contextUsage.tokens / contextWindow * 100 : 0,
66213
+ contextWindow
66214
+ });
66016
66215
  host.updateTerminalTitle();
66017
66216
  host.chatContainer.clear();
66018
66217
  host.streamingComponent = void 0;
@@ -66193,7 +66392,7 @@ function registerBuiltinCommands(host) {
66193
66392
  });
66194
66393
  commandTree.register(["settings", "theme"], {
66195
66394
  description: "Change theme",
66196
- handler: () => host.showSettingsSelector()
66395
+ handler: () => host.showSettingsSelector("theme")
66197
66396
  });
66198
66397
  commandTree.register(["settings", "thinking"], {
66199
66398
  description: "Set thinking level (off/minimal/low/medium/high/xhigh)",
@@ -66223,14 +66422,22 @@ function registerBuiltinCommands(host) {
66223
66422
  description: "Disable an extension by identity (writes settings)",
66224
66423
  handler: (_2, args) => host.handleExtensionsCommand(`disable ${args || ""}`)
66225
66424
  });
66226
- commandTree.register(["skills", "list"], {
66227
- description: "List available skills",
66425
+ commandTree.register(["skills"], {
66426
+ description: "Manage skills: list, toggle enable/disable, invoke",
66228
66427
  handler: () => host.handleSkillsCommand()
66229
66428
  });
66429
+ commandTree.register(["skills", "list"], {
66430
+ description: "List available skills (plain text)",
66431
+ handler: () => host.handleSkillsListCommand()
66432
+ });
66230
66433
  commandTree.register(["skills", "use"], {
66231
66434
  description: "Use a skill",
66232
66435
  handler: (_2, args) => host.handleSkillCommand(args || "")
66233
66436
  });
66437
+ commandTree.register(["skills", "create"], {
66438
+ description: "Create a new skill from a template",
66439
+ handler: (_2, args) => host.handleSkillsCreate(args || "")
66440
+ });
66234
66441
  commandTree.register(["skills", "reload"], {
66235
66442
  description: "Reload skills from disk",
66236
66443
  handler: async () => {
@@ -66259,12 +66466,16 @@ function registerBuiltinCommands(host) {
66259
66466
  description: "List runtime tools and manage enable/disable toggles",
66260
66467
  handler: (_2, args) => host.handleToolsCommand(args || "")
66261
66468
  });
66469
+ commandTree.register(["tools", "list"], {
66470
+ description: "List runtime tools and their state (plain text)",
66471
+ handler: (_2, args) => host.handleToolsCommand(args ? `list ${args}` : "list")
66472
+ });
66262
66473
  commandTree.register(["tools", "enable"], {
66263
- description: "Enable a tool by name (writes settings; next start)",
66474
+ description: "Enable a tool by name (writes settings; applies now)",
66264
66475
  handler: (_2, args) => host.handleToolsCommand(`enable ${args || ""}`)
66265
66476
  });
66266
66477
  commandTree.register(["tools", "disable"], {
66267
- description: "Disable a tool by name (writes settings; next start)",
66478
+ description: "Disable a tool by name (writes settings; applies now)",
66268
66479
  handler: (_2, args) => host.handleToolsCommand(`disable ${args || ""}`)
66269
66480
  });
66270
66481
  commandTree.register(["debugs", "cache-test"], {
@@ -71635,9 +71846,12 @@ var InteractiveMode = class {
71635
71846
  const rebuilt = this.rebuildSkillAwareTools(completeToolSet);
71636
71847
  const currentTools = this.extensionRunner ? wrapToolsWithExtensions(rebuilt.currentTools, this.extensionRunner) : rebuilt.currentTools;
71637
71848
  const originalTools = this.extensionRunner ? wrapToolsWithExtensions(rebuilt.originalTools, this.extensionRunner) : rebuilt.originalTools;
71638
- const activeTools = selectToolsForRuntimeMode(
71639
- currentTools,
71640
- this.planModeEnabled ? "plan" : "normal"
71849
+ const activeTools = filterEnabledTools(
71850
+ selectToolsForRuntimeMode(
71851
+ currentTools,
71852
+ this.planModeEnabled ? "plan" : "normal"
71853
+ ),
71854
+ this.options.settingsManager
71641
71855
  );
71642
71856
  this.agent.setTools(activeTools);
71643
71857
  this.allTools = [...currentTools];
@@ -71648,7 +71862,8 @@ var InteractiveMode = class {
71648
71862
  planMode: this.planModeEnabled ? "planning" : "off",
71649
71863
  filesystemScope: this.yoloMode ? "all" : "workspace",
71650
71864
  toolCount: activeTools.length,
71651
- availableToolCount: currentTools.length
71865
+ availableToolCount: currentTools.length,
71866
+ disabledToolCount: this.options.settingsManager.getDisabledTools().length
71652
71867
  });
71653
71868
  }
71654
71869
  noteRuntimeModeChanged(trigger) {
@@ -71901,8 +72116,8 @@ var InteractiveMode = class {
71901
72116
  async selectMentionOption(option) {
71902
72117
  return selectMentionOption(this.filePipelineHost, option);
71903
72118
  }
71904
- showSettingsSelector() {
71905
- return showSettingsSelector(this);
72119
+ showSettingsSelector(focusItem) {
72120
+ return showSettingsSelector(this, focusItem);
71906
72121
  }
71907
72122
  async handleModelCommand(searchTerm) {
71908
72123
  return handleModelCommand(this, searchTerm);
@@ -72013,6 +72228,9 @@ var InteractiveMode = class {
72013
72228
  handleSkillsCommand() {
72014
72229
  return handleSkillsCommand(this);
72015
72230
  }
72231
+ handleSkillsListCommand() {
72232
+ return handleSkillsListCommand(this);
72233
+ }
72016
72234
  handleSkillCommand(name) {
72017
72235
  return handleSkillCommand(this, name);
72018
72236
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cydm/pie",
3
- "version": "1.0.47",
3
+ "version": "1.0.48",
4
4
  "description": "Pie AI Agent CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  "test": "npm run test:offline",
19
19
  "pretest:offline": "npm run build --workspace @pie/agent-framework && npm run build --workspace @pie/shared-headless-capabilities && npm run build",
20
20
  "posttest:offline": "node --test --import tsx test/remote-model-catalog.test.ts",
21
- "test:offline": "node --test --import tsx test/core-services.test.ts test/cli.test.ts test/cli-auto-compaction.test.ts test/execution-state-manager.test.ts test/queued-steer-execution-state.test.ts test/architecture-boundaries.test.ts test/attachment-resolver.test.ts test/kimi-attachment-extension.test.ts test/document-attachment-extension.test.ts test/auto-compact-overflow.test.ts test/hierarchical-command-system.test.ts test/extensions-enablement.test.ts test/skill-tool-enablement.test.ts test/message-queue-unit.test.ts test/evals-harness.test.ts test/session-trace.test.ts test/runtime-mode-context.test.ts test/tool-policy.test.ts test/retry-policy.test.ts test/yolo-command.test.ts test/footer-lifecycle.test.ts test/files-utils.test.ts test/source-size-guard.test.ts test/runtime-logger.test.ts test/release-readiness.test.ts test/package-artifacts.test.ts test/code-intel-typescript-provider.test.ts test/init-extension-contract.test.ts test/skills-runtime.test.ts test/skills-agents-discovery.test.ts test/skills-symlink.test.ts test/model-overrides.test.ts test/auth-storage.test.ts test/extension-register-provider.test.ts test/extension-reload.test.ts test/extension-terminal-input.test.ts test/extension-session-before-events.test.ts test/extension-widgets.test.ts test/extension-render-hint.test.ts test/extension-oauth-restore.test.ts test/cross-host-parity.test.ts test/tool-name-rendering.test.ts test/session-selector.test.ts test/terminal-title.test.ts test/interactive-controller-title.test.ts test/todo-title.terminal.test.ts test/plan-mode-extension.test.ts test/tools-registry.test.ts test/system-prompt-contract.test.ts test/direct-bash.test.ts test/terminal-bell.test.ts test/calm-mode.test.ts test/project-root.test.ts test/guard-advisory-context.test.ts test/daily-agent-smoke.terminal.test.ts",
21
+ "test:offline": "node --test --import tsx test/core-services.test.ts test/cli.test.ts test/cli-auto-compaction.test.ts test/execution-state-manager.test.ts test/queued-steer-execution-state.test.ts test/architecture-boundaries.test.ts test/attachment-resolver.test.ts test/kimi-attachment-extension.test.ts test/document-attachment-extension.test.ts test/auto-compact-overflow.test.ts test/hierarchical-command-system.test.ts test/extensions-enablement.test.ts test/skill-tool-enablement.test.ts test/message-queue-unit.test.ts test/evals-harness.test.ts test/session-trace.test.ts test/runtime-mode-context.test.ts test/tool-policy.test.ts test/retry-policy.test.ts test/yolo-command.test.ts test/footer-lifecycle.test.ts test/files-utils.test.ts test/source-size-guard.test.ts test/runtime-logger.test.ts test/release-readiness.test.ts test/package-artifacts.test.ts test/code-intel-typescript-provider.test.ts test/init-extension-contract.test.ts test/skills-runtime.test.ts test/skills-agents-discovery.test.ts test/skills-symlink.test.ts test/model-overrides.test.ts test/auth-storage.test.ts test/extension-register-provider.test.ts test/extension-reload.test.ts test/extension-terminal-input.test.ts test/extension-session-before-events.test.ts test/extension-widgets.test.ts test/extension-render-hint.test.ts test/extension-oauth-restore.test.ts test/cross-host-parity.test.ts test/tool-name-rendering.test.ts test/session-selector.test.ts test/terminal-title.test.ts test/interactive-controller-title.test.ts test/todo-title.terminal.test.ts test/plan-mode-extension.test.ts test/tools-registry.test.ts test/system-prompt-contract.test.ts test/direct-bash.test.ts test/terminal-bell.test.ts test/calm-mode.test.ts test/project-root.test.ts test/guard-advisory-context.test.ts test/clear-command-usage.test.ts test/daily-agent-smoke.terminal.test.ts test/slash-menu-toggles.terminal.test.ts",
22
22
  "test:all": "npm run test:offline && npm run test:terminal:vitest",
23
23
  "test:terminal:vitest": "vitest run test/mention-file-select.terminal.test.ts test/tool-partial-display.terminal.test.ts test/viewport-position-final.test.ts test/resume-header.terminal.test.ts --no-file-parallelism",
24
24
  "test:todo-tui": "PIE_REAL_TODO_TUI=1 node --test --import tsx test/todo-closure.terminal.test.ts",