mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 10:01:21 +00:00
Release v0.7.23
This commit is contained in:
parent
ddb5a4497f
commit
b3d4478b61
13 changed files with 189 additions and 40 deletions
28
package-lock.json
generated
28
package-lock.json
generated
|
|
@ -3195,11 +3195,11 @@
|
||||||
},
|
},
|
||||||
"packages/agent": {
|
"packages/agent": {
|
||||||
"name": "@mariozechner/pi-agent",
|
"name": "@mariozechner/pi-agent",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-ai": "^0.7.21",
|
"@mariozechner/pi-ai": "^0.7.22",
|
||||||
"@mariozechner/pi-tui": "^0.7.21"
|
"@mariozechner/pi-tui": "^0.7.22"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.3.0",
|
"@types/node": "^24.3.0",
|
||||||
|
|
@ -3225,7 +3225,7 @@
|
||||||
},
|
},
|
||||||
"packages/ai": {
|
"packages/ai": {
|
||||||
"name": "@mariozechner/pi-ai",
|
"name": "@mariozechner/pi-ai",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.61.0",
|
"@anthropic-ai/sdk": "^0.61.0",
|
||||||
|
|
@ -3272,11 +3272,11 @@
|
||||||
},
|
},
|
||||||
"packages/coding-agent": {
|
"packages/coding-agent": {
|
||||||
"name": "@mariozechner/pi-coding-agent",
|
"name": "@mariozechner/pi-coding-agent",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent": "^0.7.21",
|
"@mariozechner/pi-agent": "^0.7.22",
|
||||||
"@mariozechner/pi-ai": "^0.7.21",
|
"@mariozechner/pi-ai": "^0.7.22",
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.5.0",
|
||||||
"diff": "^8.0.2",
|
"diff": "^8.0.2",
|
||||||
"glob": "^11.0.3"
|
"glob": "^11.0.3"
|
||||||
|
|
@ -3319,10 +3319,10 @@
|
||||||
},
|
},
|
||||||
"packages/pods": {
|
"packages/pods": {
|
||||||
"name": "@mariozechner/pi",
|
"name": "@mariozechner/pi",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent": "^0.7.21",
|
"@mariozechner/pi-agent": "^0.7.22",
|
||||||
"chalk": "^5.5.0"
|
"chalk": "^5.5.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
@ -3345,7 +3345,7 @@
|
||||||
},
|
},
|
||||||
"packages/proxy": {
|
"packages/proxy": {
|
||||||
"name": "@mariozechner/pi-proxy",
|
"name": "@mariozechner/pi-proxy",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/node-server": "^1.14.0",
|
"@hono/node-server": "^1.14.0",
|
||||||
"hono": "^4.6.16"
|
"hono": "^4.6.16"
|
||||||
|
|
@ -3361,7 +3361,7 @@
|
||||||
},
|
},
|
||||||
"packages/tui": {
|
"packages/tui": {
|
||||||
"name": "@mariozechner/pi-tui",
|
"name": "@mariozechner/pi-tui",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
|
|
@ -3400,12 +3400,12 @@
|
||||||
},
|
},
|
||||||
"packages/web-ui": {
|
"packages/web-ui": {
|
||||||
"name": "@mariozechner/pi-web-ui",
|
"name": "@mariozechner/pi-web-ui",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lmstudio/sdk": "^1.5.0",
|
"@lmstudio/sdk": "^1.5.0",
|
||||||
"@mariozechner/pi-ai": "^0.7.21",
|
"@mariozechner/pi-ai": "^0.7.22",
|
||||||
"@mariozechner/pi-tui": "^0.7.21",
|
"@mariozechner/pi-tui": "^0.7.22",
|
||||||
"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",
|
"name": "@mariozechner/pi-agent",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"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.7.22",
|
"@mariozechner/pi-ai": "^0.7.23",
|
||||||
"@mariozechner/pi-tui": "^0.7.22"
|
"@mariozechner/pi-tui": "^0.7.23"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ai",
|
"ai",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-ai",
|
"name": "@mariozechner/pi-ai",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"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",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,21 @@
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.7.23] - 2025-11-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Update Notifications**: Interactive mode now checks for new versions on startup and displays a notification if an update is available.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **System Prompt**: Updated system prompt to instruct agent to output plain text summaries directly instead of using cat or bash commands to display what it did.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **File Path Completion**: Removed 10-file limit in tab completion selector. All matching files and directories now appear in the completion list.
|
||||||
|
- **Absolute Path Completion**: Fixed tab completion for absolute paths (e.g., `/Applications`). Absolute paths in the middle of text (like "hey /") now complete correctly. Also fixed crashes when trying to stat inaccessible files (like macOS `.VolumeIcon.icns`) during directory traversal.
|
||||||
|
|
||||||
## [0.7.22] - 2025-11-19
|
## [0.7.22] - 2025-11-19
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-coding-agent",
|
"name": "@mariozechner/pi-coding-agent",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"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": {
|
||||||
|
|
@ -21,8 +21,8 @@
|
||||||
"prepublishOnly": "npm run clean && npm run build"
|
"prepublishOnly": "npm run clean && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mariozechner/pi-agent": "^0.7.22",
|
"@mariozechner/pi-agent": "^0.7.23",
|
||||||
"@mariozechner/pi-ai": "^0.7.22",
|
"@mariozechner/pi-ai": "^0.7.23",
|
||||||
"chalk": "^5.5.0",
|
"chalk": "^5.5.0",
|
||||||
"diff": "^8.0.2",
|
"diff": "^8.0.2",
|
||||||
"glob": "^11.0.3"
|
"glob": "^11.0.3"
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,7 @@ Guidelines:
|
||||||
- Use write only for new files or complete rewrites
|
- Use write only for new files or complete rewrites
|
||||||
- Be concise in your responses
|
- Be concise in your responses
|
||||||
- Show file paths clearly when working with files
|
- Show file paths clearly when working with files
|
||||||
|
- When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did
|
||||||
|
|
||||||
Documentation:
|
Documentation:
|
||||||
- Your own documentation (including custom model setup) is at: ${readmePath}
|
- Your own documentation (including custom model setup) is at: ${readmePath}
|
||||||
|
|
@ -308,6 +309,25 @@ function loadProjectContextFiles(): Array<{ path: string; content: string }> {
|
||||||
return contextFiles;
|
return contextFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkForNewVersion(currentVersion: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const response = await fetch("https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest");
|
||||||
|
if (!response.ok) return null;
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const latestVersion = data.version;
|
||||||
|
|
||||||
|
if (latestVersion && latestVersion !== currentVersion) {
|
||||||
|
return latestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
// Silently fail - don't disrupt the user experience
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function selectSession(sessionManager: SessionManager): Promise<string | null> {
|
async function selectSession(sessionManager: SessionManager): Promise<string | null> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const ui = new TUI(new ProcessTerminal());
|
const ui = new TUI(new ProcessTerminal());
|
||||||
|
|
@ -344,8 +364,9 @@ async function runInteractiveMode(
|
||||||
version: string,
|
version: string,
|
||||||
changelogMarkdown: string | null = null,
|
changelogMarkdown: string | null = null,
|
||||||
modelFallbackMessage: string | null = null,
|
modelFallbackMessage: string | null = null,
|
||||||
|
newVersion: string | null = null,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const renderer = new TuiRenderer(agent, sessionManager, settingsManager, version, changelogMarkdown);
|
const renderer = new TuiRenderer(agent, sessionManager, settingsManager, version, changelogMarkdown, newVersion);
|
||||||
|
|
||||||
// Initialize TUI
|
// Initialize TUI
|
||||||
await renderer.init();
|
await renderer.init();
|
||||||
|
|
@ -752,6 +773,17 @@ export async function main(args: string[]) {
|
||||||
// RPC mode - headless operation
|
// RPC mode - headless operation
|
||||||
await runRpcMode(agent, sessionManager);
|
await runRpcMode(agent, sessionManager);
|
||||||
} else if (isInteractive) {
|
} else if (isInteractive) {
|
||||||
|
// Check for new version (don't block startup if it takes too long)
|
||||||
|
let newVersion: string | null = null;
|
||||||
|
try {
|
||||||
|
newVersion = await Promise.race([
|
||||||
|
checkForNewVersion(VERSION),
|
||||||
|
new Promise<null>((resolve) => setTimeout(() => resolve(null), 1000)), // 1 second timeout
|
||||||
|
]);
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
|
||||||
// Check if we should show changelog (only in interactive mode, only for new sessions)
|
// Check if we should show changelog (only in interactive mode, only for new sessions)
|
||||||
let changelogMarkdown: string | null = null;
|
let changelogMarkdown: string | null = null;
|
||||||
if (!parsed.continue && !parsed.resume) {
|
if (!parsed.continue && !parsed.resume) {
|
||||||
|
|
@ -789,6 +821,7 @@ export async function main(args: string[]) {
|
||||||
VERSION,
|
VERSION,
|
||||||
changelogMarkdown,
|
changelogMarkdown,
|
||||||
modelFallbackMessage,
|
modelFallbackMessage,
|
||||||
|
newVersion,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// CLI mode with messages
|
// CLI mode with messages
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ export class TuiRenderer {
|
||||||
private onInterruptCallback?: () => void;
|
private onInterruptCallback?: () => void;
|
||||||
private lastSigintTime = 0;
|
private lastSigintTime = 0;
|
||||||
private changelogMarkdown: string | null = null;
|
private changelogMarkdown: string | null = null;
|
||||||
|
private newVersion: string | null = null;
|
||||||
|
|
||||||
// Streaming message tracking
|
// Streaming message tracking
|
||||||
private streamingComponent: AssistantMessageComponent | null = null;
|
private streamingComponent: AssistantMessageComponent | null = null;
|
||||||
|
|
@ -79,11 +80,13 @@ export class TuiRenderer {
|
||||||
settingsManager: SettingsManager,
|
settingsManager: SettingsManager,
|
||||||
version: string,
|
version: string,
|
||||||
changelogMarkdown: string | null = null,
|
changelogMarkdown: string | null = null,
|
||||||
|
newVersion: string | null = null,
|
||||||
) {
|
) {
|
||||||
this.agent = agent;
|
this.agent = agent;
|
||||||
this.sessionManager = sessionManager;
|
this.sessionManager = sessionManager;
|
||||||
this.settingsManager = settingsManager;
|
this.settingsManager = settingsManager;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
|
this.newVersion = newVersion;
|
||||||
this.changelogMarkdown = changelogMarkdown;
|
this.changelogMarkdown = changelogMarkdown;
|
||||||
this.ui = new TUI(new ProcessTerminal());
|
this.ui = new TUI(new ProcessTerminal());
|
||||||
this.chatContainer = new Container();
|
this.chatContainer = new Container();
|
||||||
|
|
@ -181,6 +184,23 @@ export class TuiRenderer {
|
||||||
this.ui.addChild(header);
|
this.ui.addChild(header);
|
||||||
this.ui.addChild(new Spacer(1));
|
this.ui.addChild(new Spacer(1));
|
||||||
|
|
||||||
|
// Add new version notification if available
|
||||||
|
if (this.newVersion) {
|
||||||
|
this.ui.addChild(new DynamicBorder(chalk.yellow));
|
||||||
|
this.ui.addChild(
|
||||||
|
new Text(
|
||||||
|
chalk.bold.yellow("Update Available") +
|
||||||
|
"\n" +
|
||||||
|
chalk.gray(`New version ${this.newVersion} is available. Run: `) +
|
||||||
|
chalk.cyan("npm install -g @mariozechner/pi-coding-agent"),
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.ui.addChild(new Spacer(1));
|
||||||
|
this.ui.addChild(new DynamicBorder(chalk.yellow));
|
||||||
|
}
|
||||||
|
|
||||||
// Add changelog if provided
|
// Add changelog if provided
|
||||||
if (this.changelogMarkdown) {
|
if (this.changelogMarkdown) {
|
||||||
this.ui.addChild(new DynamicBorder(chalk.cyan));
|
this.ui.addChild(new DynamicBorder(chalk.cyan));
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi",
|
"name": "@mariozechner/pi",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"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": "^0.7.22",
|
"@mariozechner/pi-agent": "^0.7.23",
|
||||||
"chalk": "^5.5.0"
|
"chalk": "^5.5.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {}
|
"devDependencies": {}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-proxy",
|
"name": "@mariozechner/pi-proxy",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"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.7.22",
|
"version": "0.7.23",
|
||||||
"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",
|
||||||
|
|
|
||||||
|
|
@ -286,10 +286,12 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
// Match paths - including those ending with /, ~/, or any word at end for forced extraction
|
// Match paths - including those ending with /, ~/, or any word at end for forced extraction
|
||||||
// This regex captures:
|
// This regex captures:
|
||||||
// - Paths starting from beginning of line or after space/quote/equals
|
// - Paths starting from beginning of line or after space/quote/equals
|
||||||
// - Optional ./ or ../ or ~/ prefix (including the trailing slash for ~/)
|
// - Absolute paths starting with /
|
||||||
|
// - Relative paths with ./ or ../
|
||||||
|
// - Home directory paths with ~/
|
||||||
// - The path itself (can include / in the middle)
|
// - The path itself (can include / in the middle)
|
||||||
// - For forced extraction, capture any word at the end
|
// - For forced extraction, capture any word at the end
|
||||||
const matches = text.match(/(?:^|[\s"'=])((?:~\/|\.{0,2}\/?)?(?:[^\s"'=]*\/?)*[^\s"'=]*)$/);
|
const matches = text.match(/(?:^|[\s"'=])((?:\/|~\/|\.{1,2}\/)?(?:[^\s"'=]*\/?)*[^\s"'=]*)$/);
|
||||||
if (!matches) {
|
if (!matches) {
|
||||||
// If forced extraction and no matches, return empty string to trigger from current dir
|
// If forced extraction and no matches, return empty string to trigger from current dir
|
||||||
return forceExtract ? "" : null;
|
return forceExtract ? "" : null;
|
||||||
|
|
@ -354,10 +356,11 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
expandedPrefix === "../" ||
|
expandedPrefix === "../" ||
|
||||||
expandedPrefix === "~" ||
|
expandedPrefix === "~" ||
|
||||||
expandedPrefix === "~/" ||
|
expandedPrefix === "~/" ||
|
||||||
|
expandedPrefix === "/" ||
|
||||||
prefix === "@"
|
prefix === "@"
|
||||||
) {
|
) {
|
||||||
// Complete from specified position
|
// Complete from specified position
|
||||||
if (prefix.startsWith("~")) {
|
if (prefix.startsWith("~") || expandedPrefix === "/") {
|
||||||
searchDir = expandedPrefix;
|
searchDir = expandedPrefix;
|
||||||
} else {
|
} else {
|
||||||
searchDir = join(this.basePath, expandedPrefix);
|
searchDir = join(this.basePath, expandedPrefix);
|
||||||
|
|
@ -365,7 +368,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
searchPrefix = "";
|
searchPrefix = "";
|
||||||
} else if (expandedPrefix.endsWith("/")) {
|
} else if (expandedPrefix.endsWith("/")) {
|
||||||
// If prefix ends with /, show contents of that directory
|
// If prefix ends with /, show contents of that directory
|
||||||
if (prefix.startsWith("~") || (isAtPrefix && expandedPrefix.startsWith("/"))) {
|
if (prefix.startsWith("~") || expandedPrefix.startsWith("/")) {
|
||||||
searchDir = expandedPrefix;
|
searchDir = expandedPrefix;
|
||||||
} else {
|
} else {
|
||||||
searchDir = join(this.basePath, expandedPrefix);
|
searchDir = join(this.basePath, expandedPrefix);
|
||||||
|
|
@ -375,7 +378,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
// Split into directory and file prefix
|
// Split into directory and file prefix
|
||||||
const dir = dirname(expandedPrefix);
|
const dir = dirname(expandedPrefix);
|
||||||
const file = basename(expandedPrefix);
|
const file = basename(expandedPrefix);
|
||||||
if (prefix.startsWith("~") || (isAtPrefix && expandedPrefix.startsWith("/"))) {
|
if (prefix.startsWith("~") || expandedPrefix.startsWith("/")) {
|
||||||
searchDir = dir;
|
searchDir = dir;
|
||||||
} else {
|
} else {
|
||||||
searchDir = join(this.basePath, dir);
|
searchDir = join(this.basePath, dir);
|
||||||
|
|
@ -392,7 +395,13 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fullPath = join(searchDir, entry);
|
const fullPath = join(searchDir, entry);
|
||||||
const isDirectory = statSync(fullPath).isDirectory();
|
let isDirectory: boolean;
|
||||||
|
try {
|
||||||
|
isDirectory = statSync(fullPath).isDirectory();
|
||||||
|
} catch (e) {
|
||||||
|
// Skip files we can't stat (permission issues, broken symlinks, etc.)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// For @ prefix, filter to only show directories and attachable files
|
// For @ prefix, filter to only show directories and attachable files
|
||||||
if (isAtPrefix && !isDirectory && !isAttachableFile(fullPath)) {
|
if (isAtPrefix && !isDirectory && !isAttachableFile(fullPath)) {
|
||||||
|
|
@ -430,6 +439,14 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
const homeRelativeDir = prefix.slice(2); // Remove ~/
|
const homeRelativeDir = prefix.slice(2); // Remove ~/
|
||||||
const dir = dirname(homeRelativeDir);
|
const dir = dirname(homeRelativeDir);
|
||||||
relativePath = "~/" + (dir === "." ? entry : join(dir, entry));
|
relativePath = "~/" + (dir === "." ? entry : join(dir, entry));
|
||||||
|
} else if (prefix.startsWith("/")) {
|
||||||
|
// Absolute path - construct properly
|
||||||
|
const dir = dirname(prefix);
|
||||||
|
if (dir === "/") {
|
||||||
|
relativePath = "/" + entry;
|
||||||
|
} else {
|
||||||
|
relativePath = dir + "/" + entry;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
relativePath = join(dirname(prefix), entry);
|
relativePath = join(dirname(prefix), entry);
|
||||||
}
|
}
|
||||||
|
|
@ -458,7 +475,7 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
return a.label.localeCompare(b.label);
|
return a.label.localeCompare(b.label);
|
||||||
});
|
});
|
||||||
|
|
||||||
return suggestions.slice(0, 10); // Limit to 10 suggestions
|
return suggestions;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Directory doesn't exist or not accessible
|
// Directory doesn't exist or not accessible
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -474,8 +491,8 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
const currentLine = lines[cursorLine] || "";
|
const currentLine = lines[cursorLine] || "";
|
||||||
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
||||||
|
|
||||||
// Don't trigger if we're in a slash command
|
// Don't trigger if we're typing a slash command at the start of the line
|
||||||
if (textBeforeCursor.startsWith("/") && !textBeforeCursor.includes(" ")) {
|
if (textBeforeCursor.trim().startsWith("/") && !textBeforeCursor.trim().includes(" ")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -499,8 +516,8 @@ export class CombinedAutocompleteProvider implements AutocompleteProvider {
|
||||||
const currentLine = lines[cursorLine] || "";
|
const currentLine = lines[cursorLine] || "";
|
||||||
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
const textBeforeCursor = currentLine.slice(0, cursorCol);
|
||||||
|
|
||||||
// Don't trigger if we're in a slash command
|
// Don't trigger if we're typing a slash command at the start of the line
|
||||||
if (textBeforeCursor.startsWith("/") && !textBeforeCursor.includes(" ")) {
|
if (textBeforeCursor.trim().startsWith("/") && !textBeforeCursor.trim().includes(" ")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
64
packages/tui/test/autocomplete.test.ts
Normal file
64
packages/tui/test/autocomplete.test.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import assert from "node:assert";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
import { CombinedAutocompleteProvider } from "../src/autocomplete.js";
|
||||||
|
|
||||||
|
describe("CombinedAutocompleteProvider", () => {
|
||||||
|
describe("extractPathPrefix", () => {
|
||||||
|
it("extracts / from 'hey /' when forced", () => {
|
||||||
|
const provider = new CombinedAutocompleteProvider([], "/tmp");
|
||||||
|
const lines = ["hey /"];
|
||||||
|
const cursorLine = 0;
|
||||||
|
const cursorCol = 5; // After the "/"
|
||||||
|
|
||||||
|
const result = provider.getForceFileSuggestions(lines, cursorLine, cursorCol);
|
||||||
|
|
||||||
|
assert.notEqual(result, null, "Should return suggestions for root directory");
|
||||||
|
if (result) {
|
||||||
|
assert.strictEqual(result.prefix, "/", "Prefix should be '/'");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("extracts /A from '/A' when forced", () => {
|
||||||
|
const provider = new CombinedAutocompleteProvider([], "/tmp");
|
||||||
|
const lines = ["/A"];
|
||||||
|
const cursorLine = 0;
|
||||||
|
const cursorCol = 2; // After the "A"
|
||||||
|
|
||||||
|
const result = provider.getForceFileSuggestions(lines, cursorLine, cursorCol);
|
||||||
|
|
||||||
|
console.log("Result:", result);
|
||||||
|
// This might return null if /A doesn't match anything, which is fine
|
||||||
|
// We're mainly testing that the prefix extraction works
|
||||||
|
if (result) {
|
||||||
|
assert.strictEqual(result.prefix, "/A", "Prefix should be '/A'");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not trigger for slash commands", () => {
|
||||||
|
const provider = new CombinedAutocompleteProvider([], "/tmp");
|
||||||
|
const lines = ["/model"];
|
||||||
|
const cursorLine = 0;
|
||||||
|
const cursorCol = 6; // After "model"
|
||||||
|
|
||||||
|
const result = provider.getForceFileSuggestions(lines, cursorLine, cursorCol);
|
||||||
|
|
||||||
|
console.log("Result:", result);
|
||||||
|
assert.strictEqual(result, null, "Should not trigger for slash commands");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("triggers for absolute paths after slash command argument", () => {
|
||||||
|
const provider = new CombinedAutocompleteProvider([], "/tmp");
|
||||||
|
const lines = ["/command /"];
|
||||||
|
const cursorLine = 0;
|
||||||
|
const cursorCol = 10; // After the second "/"
|
||||||
|
|
||||||
|
const result = provider.getForceFileSuggestions(lines, cursorLine, cursorCol);
|
||||||
|
|
||||||
|
console.log("Result:", result);
|
||||||
|
assert.notEqual(result, null, "Should trigger for absolute paths in command arguments");
|
||||||
|
if (result) {
|
||||||
|
assert.strictEqual(result.prefix, "/", "Prefix should be '/'");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mariozechner/pi-web-ui",
|
"name": "@mariozechner/pi-web-ui",
|
||||||
"version": "0.7.22",
|
"version": "0.7.23",
|
||||||
"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.7.22",
|
"@mariozechner/pi-ai": "^0.7.23",
|
||||||
"@mariozechner/pi-tui": "^0.7.22",
|
"@mariozechner/pi-tui": "^0.7.23",
|
||||||
"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