mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-17 07:03:25 +00:00
Release v0.11.4
This commit is contained in:
parent
285c657b70
commit
e25420a4c8
17 changed files with 154 additions and 102 deletions
14
README.md
14
README.md
|
|
@ -59,9 +59,10 @@ These commands:
|
||||||
|
|
||||||
Complete release process:
|
Complete release process:
|
||||||
|
|
||||||
1. **Update CHANGELOG.md** (if changes affect coding-agent):
|
1. **Add changes to CHANGELOG.md** (if changes affect coding-agent):
|
||||||
```bash
|
```bash
|
||||||
# Add your changes to the [Unreleased] section in packages/coding-agent/CHANGELOG.md
|
# Add your changes to the [Unreleased] section in packages/coding-agent/CHANGELOG.md
|
||||||
|
# Always add new entries under [Unreleased], never under already-released versions
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Bump version** (all packages):
|
2. **Bump version** (all packages):
|
||||||
|
|
@ -71,10 +72,11 @@ Complete release process:
|
||||||
npm run version:major # For breaking changes
|
npm run version:major # For breaking changes
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Update CHANGELOG.md version** (if changes affect coding-agent):
|
3. **Finalize CHANGELOG.md for release** (if changes affect coding-agent):
|
||||||
```bash
|
```bash
|
||||||
# Move the [Unreleased] section to the new version number with today's date
|
# Change [Unreleased] to the new version number with today's date
|
||||||
# e.g., ## [0.7.16] - 2025-11-17
|
# e.g., ## [0.7.16] - 2025-11-17
|
||||||
|
# Then add a new empty [Unreleased] section at the top
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Commit and tag**:
|
4. **Commit and tag**:
|
||||||
|
|
@ -91,6 +93,12 @@ Complete release process:
|
||||||
npm run publish # Publish all packages to npm
|
npm run publish # Publish all packages to npm
|
||||||
```
|
```
|
||||||
|
|
||||||
|
6. **Add new [Unreleased] section** (for next development cycle):
|
||||||
|
```bash
|
||||||
|
# Add a new [Unreleased] section at the top of CHANGELOG.md
|
||||||
|
# Commit: git commit -am "Add [Unreleased] section"
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
36
package-lock.json
generated
36
package-lock.json
generated
|
|
@ -6074,11 +6074,11 @@
|
||||||
},
|
},
|
||||||
"packages/agent": {
|
"packages/agent": {
|
||||||
"name": "@mariozechner/pi-agent-core",
|
"name": "@mariozechner/pi-agent-core",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-ai": "^0.11.2",
|
"@mariozechner/pi-ai": "^0.11.3",
|
||||||
"@mariozechner/pi-tui": "^0.11.2"
|
"@mariozechner/pi-tui": "^0.11.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.3.0",
|
"@types/node": "^24.3.0",
|
||||||
|
|
@ -6108,7 +6108,7 @@
|
||||||
},
|
},
|
||||||
"packages/ai": {
|
"packages/ai": {
|
||||||
"name": "@mariozechner/pi-ai",
|
"name": "@mariozechner/pi-ai",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.61.0",
|
"@anthropic-ai/sdk": "^0.61.0",
|
||||||
|
|
@ -6149,12 +6149,12 @@
|
||||||
},
|
},
|
||||||
"packages/coding-agent": {
|
"packages/coding-agent": {
|
||||||
"name": "@mariozechner/pi-coding-agent",
|
"name": "@mariozechner/pi-coding-agent",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent-core": "^0.11.2",
|
"@mariozechner/pi-agent-core": "^0.11.3",
|
||||||
"@mariozechner/pi-ai": "^0.11.2",
|
"@mariozechner/pi-ai": "^0.11.3",
|
||||||
"@mariozechner/pi-tui": "^0.11.2",
|
"@mariozechner/pi-tui": "^0.11.3",
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.5.0",
|
||||||
"diff": "^8.0.2",
|
"diff": "^8.0.2",
|
||||||
"glob": "^11.0.3"
|
"glob": "^11.0.3"
|
||||||
|
|
@ -6191,12 +6191,12 @@
|
||||||
},
|
},
|
||||||
"packages/mom": {
|
"packages/mom": {
|
||||||
"name": "@mariozechner/pi-mom",
|
"name": "@mariozechner/pi-mom",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sandbox-runtime": "^0.0.16",
|
"@anthropic-ai/sandbox-runtime": "^0.0.16",
|
||||||
"@mariozechner/pi-agent-core": "^0.11.2",
|
"@mariozechner/pi-agent-core": "^0.11.3",
|
||||||
"@mariozechner/pi-ai": "^0.11.2",
|
"@mariozechner/pi-ai": "^0.11.3",
|
||||||
"@sinclair/typebox": "^0.34.0",
|
"@sinclair/typebox": "^0.34.0",
|
||||||
"@slack/socket-mode": "^2.0.0",
|
"@slack/socket-mode": "^2.0.0",
|
||||||
"@slack/web-api": "^7.0.0",
|
"@slack/web-api": "^7.0.0",
|
||||||
|
|
@ -6234,10 +6234,10 @@
|
||||||
},
|
},
|
||||||
"packages/pods": {
|
"packages/pods": {
|
||||||
"name": "@mariozechner/pi",
|
"name": "@mariozechner/pi",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent-core": "^0.11.2",
|
"@mariozechner/pi-agent-core": "^0.11.3",
|
||||||
"chalk": "^5.5.0"
|
"chalk": "^5.5.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -6250,7 +6250,7 @@
|
||||||
},
|
},
|
||||||
"packages/proxy": {
|
"packages/proxy": {
|
||||||
"name": "@mariozechner/pi-proxy",
|
"name": "@mariozechner/pi-proxy",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/node-server": "^1.14.0",
|
"@hono/node-server": "^1.14.0",
|
||||||
"hono": "^4.6.16"
|
"hono": "^4.6.16"
|
||||||
|
|
@ -6266,7 +6266,7 @@
|
||||||
},
|
},
|
||||||
"packages/tui": {
|
"packages/tui": {
|
||||||
"name": "@mariozechner/pi-tui",
|
"name": "@mariozechner/pi-tui",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
|
|
@ -6310,12 +6310,12 @@
|
||||||
},
|
},
|
||||||
"packages/web-ui": {
|
"packages/web-ui": {
|
||||||
"name": "@mariozechner/pi-web-ui",
|
"name": "@mariozechner/pi-web-ui",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lmstudio/sdk": "^1.5.0",
|
"@lmstudio/sdk": "^1.5.0",
|
||||||
"@mariozechner/pi-ai": "^0.11.2",
|
"@mariozechner/pi-ai": "^0.11.3",
|
||||||
"@mariozechner/pi-tui": "^0.11.2",
|
"@mariozechner/pi-tui": "^0.11.3",
|
||||||
"docx-preview": "^0.3.7",
|
"docx-preview": "^0.3.7",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide": "^0.544.0",
|
"lucide": "^0.544.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-agent-core",
|
"name": "@mariozechner/pi-agent-core",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
"prepublishOnly": "npm run clean && npm run build"
|
"prepublishOnly": "npm run clean && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-ai": "^0.11.3",
|
"@mariozechner/pi-ai": "^0.11.4",
|
||||||
"@mariozechner/pi-tui": "^0.11.3"
|
"@mariozechner/pi-tui": "^0.11.4"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ai",
|
"ai",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-ai",
|
"name": "@mariozechner/pi-ai",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.11.4] - 2025-12-01
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
|
||||||
|
- **TUI Crash Diagnostics**: When a render error occurs (line exceeds terminal width), all rendered lines are now written to `~/.pi/agent/pi-crash.log` with their indices and visible widths for easier debugging.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Session Selector Crash with Wide Characters**: Fixed crash when running `pi -r` to resume sessions containing emojis, CJK characters, or other wide Unicode characters. The session list was using character count instead of visible terminal width for truncation, causing lines to exceed terminal width. Added `truncateToWidth()` utility that properly handles ANSI codes and wide characters. ([#85](https://github.com/badlogic/pi-mono/issues/85))
|
||||||
|
|
||||||
## [0.11.3] - 2025-12-01
|
## [0.11.3] - 2025-12-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-coding-agent",
|
"name": "@mariozechner/pi-coding-agent",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -22,9 +22,9 @@
|
||||||
"prepublishOnly": "npm run clean && npm run build"
|
"prepublishOnly": "npm run clean && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent-core": "^0.11.3",
|
"@mariozechner/pi-agent-core": "^0.11.4",
|
||||||
"@mariozechner/pi-ai": "^0.11.3",
|
"@mariozechner/pi-ai": "^0.11.4",
|
||||||
"@mariozechner/pi-tui": "^0.11.3",
|
"@mariozechner/pi-tui": "^0.11.4",
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.5.0",
|
||||||
"diff": "^8.0.2",
|
"diff": "^8.0.2",
|
||||||
"glob": "^11.0.3"
|
"glob": "^11.0.3"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { type Component, Container, Input, Spacer, Text } from "@mariozechner/pi-tui";
|
import { type Component, Container, Input, Spacer, Text, truncateToWidth } from "@mariozechner/pi-tui";
|
||||||
import type { SessionManager } from "../session-manager.js";
|
import type { SessionManager } from "../session-manager.js";
|
||||||
import { theme } from "../theme/theme.js";
|
import { theme } from "../theme/theme.js";
|
||||||
import { DynamicBorder } from "./dynamic-border.js";
|
import { DynamicBorder } from "./dynamic-border.js";
|
||||||
|
|
@ -107,17 +107,17 @@ class SessionList implements Component {
|
||||||
// Normalize first message to single line
|
// Normalize first message to single line
|
||||||
const normalizedMessage = session.firstMessage.replace(/\n/g, " ").trim();
|
const normalizedMessage = session.firstMessage.replace(/\n/g, " ").trim();
|
||||||
|
|
||||||
// First line: cursor + message
|
// First line: cursor + message (truncate to visible width)
|
||||||
const cursor = isSelected ? theme.fg("accent", "› ") : " ";
|
const cursor = isSelected ? theme.fg("accent", "› ") : " ";
|
||||||
const maxMsgWidth = width - 2; // Account for cursor
|
const maxMsgWidth = width - 2; // Account for cursor (2 visible chars)
|
||||||
const truncatedMsg = normalizedMessage.substring(0, maxMsgWidth);
|
const truncatedMsg = truncateToWidth(normalizedMessage, maxMsgWidth, "...");
|
||||||
const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
|
const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
|
||||||
|
|
||||||
// Second line: metadata (dimmed)
|
// Second line: metadata (dimmed) - also truncate for safety
|
||||||
const modified = formatDate(session.modified);
|
const modified = formatDate(session.modified);
|
||||||
const msgCount = `${session.messageCount} message${session.messageCount !== 1 ? "s" : ""}`;
|
const msgCount = `${session.messageCount} message${session.messageCount !== 1 ? "s" : ""}`;
|
||||||
const metadata = ` ${modified} · ${msgCount}`;
|
const metadata = ` ${modified} · ${msgCount}`;
|
||||||
const metadataLine = theme.fg("dim", metadata);
|
const metadataLine = theme.fg("dim", truncateToWidth(metadata, width, ""));
|
||||||
|
|
||||||
lines.push(messageLine);
|
lines.push(messageLine);
|
||||||
lines.push(metadataLine);
|
lines.push(metadataLine);
|
||||||
|
|
@ -126,7 +126,8 @@ class SessionList implements Component {
|
||||||
|
|
||||||
// Add scroll indicator if needed
|
// Add scroll indicator if needed
|
||||||
if (startIndex > 0 || endIndex < this.filteredSessions.length) {
|
if (startIndex > 0 || endIndex < this.filteredSessions.length) {
|
||||||
const scrollInfo = theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`);
|
const scrollText = ` (${this.selectedIndex + 1}/${this.filteredSessions.length})`;
|
||||||
|
const scrollInfo = theme.fg("muted", truncateToWidth(scrollText, width, ""));
|
||||||
lines.push(scrollInfo);
|
lines.push(scrollInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-mom",
|
"name": "@mariozechner/pi-mom",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"description": "Slack bot that delegates messages to the pi coding agent",
|
"description": "Slack bot that delegates messages to the pi coding agent",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -21,8 +21,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sandbox-runtime": "^0.0.16",
|
"@anthropic-ai/sandbox-runtime": "^0.0.16",
|
||||||
"@mariozechner/pi-agent-core": "^0.11.3",
|
"@mariozechner/pi-agent-core": "^0.11.4",
|
||||||
"@mariozechner/pi-ai": "^0.11.3",
|
"@mariozechner/pi-ai": "^0.11.4",
|
||||||
"@sinclair/typebox": "^0.34.0",
|
"@sinclair/typebox": "^0.34.0",
|
||||||
"@slack/socket-mode": "^2.0.0",
|
"@slack/socket-mode": "^2.0.0",
|
||||||
"@slack/web-api": "^7.0.0",
|
"@slack/web-api": "^7.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi",
|
"name": "@mariozechner/pi",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"description": "CLI tool for managing vLLM deployments on GPU pods",
|
"description": "CLI tool for managing vLLM deployments on GPU pods",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent-core": "^0.11.3",
|
"@mariozechner/pi-agent-core": "^0.11.4",
|
||||||
"chalk": "^5.5.0"
|
"chalk": "^5.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {}
|
"devDependencies": {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-proxy",
|
"name": "@mariozechner/pi-proxy",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "CORS and authentication proxy for pi-ai",
|
"description": "CORS and authentication proxy for pi-ai",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-tui",
|
"name": "@mariozechner/pi-tui",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Component } from "../tui.js";
|
import type { Component } from "../tui.js";
|
||||||
|
import { truncateToWidth } from "../utils.js";
|
||||||
|
|
||||||
export interface SelectItem {
|
export interface SelectItem {
|
||||||
value: string;
|
value: string;
|
||||||
|
|
@ -77,8 +78,8 @@ export class SelectList implements Component {
|
||||||
|
|
||||||
if (item.description && width > 40) {
|
if (item.description && width > 40) {
|
||||||
// Calculate how much space we have for value + description
|
// Calculate how much space we have for value + description
|
||||||
const maxValueLength = Math.min(displayValue.length, 30);
|
const maxValueWidth = Math.min(30, width - prefixWidth - 4);
|
||||||
const truncatedValue = displayValue.substring(0, maxValueLength);
|
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
|
||||||
const spacing = " ".repeat(Math.max(1, 32 - truncatedValue.length));
|
const spacing = " ".repeat(Math.max(1, 32 - truncatedValue.length));
|
||||||
|
|
||||||
// Calculate remaining space for description using visible widths
|
// Calculate remaining space for description using visible widths
|
||||||
|
|
@ -86,18 +87,18 @@ export class SelectList implements Component {
|
||||||
const remainingWidth = width - descriptionStart - 2; // -2 for safety
|
const remainingWidth = width - descriptionStart - 2; // -2 for safety
|
||||||
|
|
||||||
if (remainingWidth > 10) {
|
if (remainingWidth > 10) {
|
||||||
const truncatedDesc = item.description.substring(0, remainingWidth);
|
const truncatedDesc = truncateToWidth(item.description, remainingWidth, "");
|
||||||
// Apply selectedText to entire line content
|
// Apply selectedText to entire line content
|
||||||
line = this.theme.selectedText("→ " + truncatedValue + spacing + truncatedDesc);
|
line = this.theme.selectedText("→ " + truncatedValue + spacing + truncatedDesc);
|
||||||
} else {
|
} else {
|
||||||
// Not enough space for description
|
// Not enough space for description
|
||||||
const maxWidth = width - prefixWidth - 2;
|
const maxWidth = width - prefixWidth - 2;
|
||||||
line = this.theme.selectedText("→ " + displayValue.substring(0, maxWidth));
|
line = this.theme.selectedText("→ " + truncateToWidth(displayValue, maxWidth, ""));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No description or not enough width
|
// No description or not enough width
|
||||||
const maxWidth = width - prefixWidth - 2;
|
const maxWidth = width - prefixWidth - 2;
|
||||||
line = this.theme.selectedText("→ " + displayValue.substring(0, maxWidth));
|
line = this.theme.selectedText("→ " + truncateToWidth(displayValue, maxWidth, ""));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const displayValue = item.label || item.value;
|
const displayValue = item.label || item.value;
|
||||||
|
|
@ -105,8 +106,8 @@ export class SelectList implements Component {
|
||||||
|
|
||||||
if (item.description && width > 40) {
|
if (item.description && width > 40) {
|
||||||
// Calculate how much space we have for value + description
|
// Calculate how much space we have for value + description
|
||||||
const maxValueLength = Math.min(displayValue.length, 30);
|
const maxValueWidth = Math.min(30, width - prefix.length - 4);
|
||||||
const truncatedValue = displayValue.substring(0, maxValueLength);
|
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
|
||||||
const spacing = " ".repeat(Math.max(1, 32 - truncatedValue.length));
|
const spacing = " ".repeat(Math.max(1, 32 - truncatedValue.length));
|
||||||
|
|
||||||
// Calculate remaining space for description
|
// Calculate remaining space for description
|
||||||
|
|
@ -114,18 +115,18 @@ export class SelectList implements Component {
|
||||||
const remainingWidth = width - descriptionStart - 2; // -2 for safety
|
const remainingWidth = width - descriptionStart - 2; // -2 for safety
|
||||||
|
|
||||||
if (remainingWidth > 10) {
|
if (remainingWidth > 10) {
|
||||||
const truncatedDesc = item.description.substring(0, remainingWidth);
|
const truncatedDesc = truncateToWidth(item.description, remainingWidth, "");
|
||||||
const descText = this.theme.description(spacing + truncatedDesc);
|
const descText = this.theme.description(spacing + truncatedDesc);
|
||||||
line = prefix + truncatedValue + descText;
|
line = prefix + truncatedValue + descText;
|
||||||
} else {
|
} else {
|
||||||
// Not enough space for description
|
// Not enough space for description
|
||||||
const maxWidth = width - prefix.length - 2;
|
const maxWidth = width - prefix.length - 2;
|
||||||
line = prefix + displayValue.substring(0, maxWidth);
|
line = prefix + truncateToWidth(displayValue, maxWidth, "");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No description or not enough width
|
// No description or not enough width
|
||||||
const maxWidth = width - prefix.length - 2;
|
const maxWidth = width - prefix.length - 2;
|
||||||
line = prefix + displayValue.substring(0, maxWidth);
|
line = prefix + truncateToWidth(displayValue, maxWidth, "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,9 +137,7 @@ export class SelectList implements Component {
|
||||||
if (startIndex > 0 || endIndex < this.filteredItems.length) {
|
if (startIndex > 0 || endIndex < this.filteredItems.length) {
|
||||||
const scrollText = ` (${this.selectedIndex + 1}/${this.filteredItems.length})`;
|
const scrollText = ` (${this.selectedIndex + 1}/${this.filteredItems.length})`;
|
||||||
// Truncate if too long for terminal
|
// Truncate if too long for terminal
|
||||||
const maxWidth = width - 2;
|
lines.push(this.theme.scrollInfo(truncateToWidth(scrollText, width - 2, "")));
|
||||||
const truncated = scrollText.substring(0, maxWidth);
|
|
||||||
lines.push(this.theme.scrollInfo(truncated));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Component } from "../tui.js";
|
import type { Component } from "../tui.js";
|
||||||
import { visibleWidth } from "../utils.js";
|
import { truncateToWidth, visibleWidth } from "../utils.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Text component that truncates to fit viewport width
|
* Text component that truncates to fit viewport width
|
||||||
|
|
@ -41,46 +41,7 @@ export class TruncatedText implements Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate text if needed (accounting for ANSI codes)
|
// Truncate text if needed (accounting for ANSI codes)
|
||||||
let displayText = singleLineText;
|
const displayText = truncateToWidth(singleLineText, availableWidth);
|
||||||
const textVisibleWidth = visibleWidth(singleLineText);
|
|
||||||
|
|
||||||
if (textVisibleWidth > availableWidth) {
|
|
||||||
// Need to truncate - walk through the string character by character
|
|
||||||
let currentWidth = 0;
|
|
||||||
let truncateAt = 0;
|
|
||||||
let i = 0;
|
|
||||||
const ellipsisWidth = 3;
|
|
||||||
const targetWidth = availableWidth - ellipsisWidth;
|
|
||||||
|
|
||||||
while (i < singleLineText.length && currentWidth < targetWidth) {
|
|
||||||
// Skip ANSI escape sequences (include them in output but don't count width)
|
|
||||||
if (singleLineText[i] === "\x1b" && singleLineText[i + 1] === "[") {
|
|
||||||
let j = i + 2;
|
|
||||||
while (j < singleLineText.length && !/[a-zA-Z]/.test(singleLineText[j])) {
|
|
||||||
j++;
|
|
||||||
}
|
|
||||||
// Include the final letter of the escape sequence
|
|
||||||
j++;
|
|
||||||
truncateAt = j;
|
|
||||||
i = j;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char = singleLineText[i];
|
|
||||||
const charWidth = visibleWidth(char);
|
|
||||||
|
|
||||||
if (currentWidth + charWidth > targetWidth) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentWidth += charWidth;
|
|
||||||
truncateAt = i + 1;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add reset code before ellipsis to prevent styling leaking into it
|
|
||||||
displayText = singleLineText.substring(0, truncateAt) + "\x1b[0m...";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add horizontal padding
|
// Add horizontal padding
|
||||||
const leftPadding = " ".repeat(this.paddingX);
|
const leftPadding = " ".repeat(this.paddingX);
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,4 @@ export { TruncatedText } from "./components/truncated-text.js";
|
||||||
export { ProcessTerminal, type Terminal } from "./terminal.js";
|
export { ProcessTerminal, type Terminal } from "./terminal.js";
|
||||||
export { type Component, Container, TUI } from "./tui.js";
|
export { type Component, Container, TUI } from "./tui.js";
|
||||||
// Utilities
|
// Utilities
|
||||||
export { visibleWidth } from "./utils.js";
|
export { truncateToWidth, visibleWidth } from "./utils.js";
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
* Minimal TUI implementation with differential rendering
|
* Minimal TUI implementation with differential rendering
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as os from "node:os";
|
||||||
|
import * as path from "node:path";
|
||||||
import type { Terminal } from "./terminal.js";
|
import type { Terminal } from "./terminal.js";
|
||||||
import { visibleWidth } from "./utils.js";
|
import { visibleWidth } from "./utils.js";
|
||||||
|
|
||||||
|
|
@ -220,7 +223,20 @@ export class TUI extends Container {
|
||||||
if (i > firstChanged) buffer += "\r\n";
|
if (i > firstChanged) buffer += "\r\n";
|
||||||
buffer += "\x1b[2K"; // Clear current line
|
buffer += "\x1b[2K"; // Clear current line
|
||||||
if (visibleWidth(newLines[i]) > width) {
|
if (visibleWidth(newLines[i]) > width) {
|
||||||
throw new Error(`Rendered line ${i} exceeds terminal width\n\n${newLines[i]}`);
|
// Log all lines to crash file for debugging
|
||||||
|
const crashLogPath = path.join(os.homedir(), ".pi", "agent", "pi-crash.log");
|
||||||
|
const crashData = [
|
||||||
|
`Crash at ${new Date().toISOString()}`,
|
||||||
|
`Terminal width: ${width}`,
|
||||||
|
`Line ${i} visible width: ${visibleWidth(newLines[i])}`,
|
||||||
|
"",
|
||||||
|
"=== All rendered lines ===",
|
||||||
|
...newLines.map((line, idx) => `[${idx}] (w=${visibleWidth(line)}) ${line}`),
|
||||||
|
"",
|
||||||
|
].join("\n");
|
||||||
|
fs.mkdirSync(path.dirname(crashLogPath), { recursive: true });
|
||||||
|
fs.writeFileSync(crashLogPath, crashData);
|
||||||
|
throw new Error(`Rendered line ${i} exceeds terminal width. Debug log written to ${crashLogPath}`);
|
||||||
}
|
}
|
||||||
buffer += newLines[i];
|
buffer += newLines[i];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -285,3 +285,60 @@ export function applyBackgroundToLine(line: string, width: number, bgFn: (text:
|
||||||
const withPadding = line + padding;
|
const withPadding = line + padding;
|
||||||
return bgFn(withPadding);
|
return bgFn(withPadding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncate text to fit within a maximum visible width, adding ellipsis if needed.
|
||||||
|
* Properly handles ANSI escape codes (they don't count toward width).
|
||||||
|
*
|
||||||
|
* @param text - Text to truncate (may contain ANSI codes)
|
||||||
|
* @param maxWidth - Maximum visible width
|
||||||
|
* @param ellipsis - Ellipsis string to append when truncating (default: "...")
|
||||||
|
* @returns Truncated text with ellipsis if it exceeded maxWidth
|
||||||
|
*/
|
||||||
|
export function truncateToWidth(text: string, maxWidth: number, ellipsis: string = "..."): string {
|
||||||
|
const textVisibleWidth = visibleWidth(text);
|
||||||
|
|
||||||
|
if (textVisibleWidth <= maxWidth) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ellipsisWidth = visibleWidth(ellipsis);
|
||||||
|
const targetWidth = maxWidth - ellipsisWidth;
|
||||||
|
|
||||||
|
if (targetWidth <= 0) {
|
||||||
|
return ellipsis.substring(0, maxWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentWidth = 0;
|
||||||
|
let truncateAt = 0;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < text.length && currentWidth < targetWidth) {
|
||||||
|
// Skip ANSI escape sequences (include them in output but don't count width)
|
||||||
|
if (text[i] === "\x1b" && text[i + 1] === "[") {
|
||||||
|
let j = i + 2;
|
||||||
|
while (j < text.length && !/[a-zA-Z]/.test(text[j]!)) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
// Include the final letter of the escape sequence
|
||||||
|
j++;
|
||||||
|
truncateAt = j;
|
||||||
|
i = j;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char = text[i]!;
|
||||||
|
const charWidth = visibleWidth(char);
|
||||||
|
|
||||||
|
if (currentWidth + charWidth > targetWidth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentWidth += charWidth;
|
||||||
|
truncateAt = i + 1;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add reset code before ellipsis to prevent styling leaking into it
|
||||||
|
return text.substring(0, truncateAt) + "\x1b[0m" + ellipsis;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-web-ui",
|
"name": "@mariozechner/pi-web-ui",
|
||||||
"version": "0.11.3",
|
"version": "0.11.4",
|
||||||
"description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai",
|
"description": "Reusable web UI components for AI chat interfaces powered by @mariozechner/pi-ai",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lmstudio/sdk": "^1.5.0",
|
"@lmstudio/sdk": "^1.5.0",
|
||||||
"@mariozechner/pi-ai": "^0.11.3",
|
"@mariozechner/pi-ai": "^0.11.4",
|
||||||
"@mariozechner/pi-tui": "^0.11.3",
|
"@mariozechner/pi-tui": "^0.11.4",
|
||||||
"docx-preview": "^0.3.7",
|
"docx-preview": "^0.3.7",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lucide": "^0.544.0",
|
"lucide": "^0.544.0",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue