Start factoring out code from initial spike

This commit is contained in:
2026-06-28 11:12:09 +02:00
parent 2dfafe23d6
commit f521fb3da9
11 changed files with 2494 additions and 1685 deletions
+57
View File
@@ -0,0 +1,57 @@
# pi-test-status
A [Pi](https://pi.dev) extension that watches for test runs and shows a pass/fail widget in the Pi UI.
## Features
- Detects `vitest` and `npm test` commands in bash tool calls
- Parses vitest output to determine pass/fail status
- Shows a live widget indicator (● green for pass, ● red for fail)
- Displays the summary line from the test output
## Installation
```bash
pi install npm:pi-test-status
```
Or with a scope:
```bash
pi install npm:@your-scope/pi-test-status
```
## Development
```bash
pi -e ./extensions/index.ts
```
Or from the package root:
```bash
pi -e .
```
## Usage
Once installed, the extension automatically activates when you run tests:
```bash
# In Pi, just run your tests — the widget appears automatically
npm test
npx vitest
vitest run
```
The widget shows:
- Green ● with summary on test pass
- Red ● with summary on test failure
## How it works
The extension subscribes to the `tool_result` event. When a bash tool call matches a test command, it parses the vitest output for pass/fail indicators and updates a Pi widget with the result.
## License
MIT
+43
View File
@@ -0,0 +1,43 @@
import {type ExtensionAPI, isBashToolResult} from "@earendil-works/pi-coding-agent";
import {isTestCommand} from "./parsing/typescript-command-output";
// noinspection JSUnusedGlobalSymbols
export default function testStatusExtension(pi: ExtensionAPI) {
// After every tool execution, check if tests just ran
pi.on("tool_result", async (event, ctx) => {
// Only care about bash commands
if (!isBashToolResult(event)) return;
if (!isTestCommand(event.input.command as string) ) return;
const textContent = event.content.find((c): c is { type: "text"; text: string } => c.type === "text");
const output = textContent?.text ?? "";
const theme = ctx.ui.theme;
// Detect pass/fail from vitest summary output
const testsPassed =
/Tests\s+.+passed/.test(output) && !/Tests\s+.+failed/.test(output);
const testsFailed =
/Tests\s+.+failed/.test(output) || event.details?.exitCode !== 0;
if (testsPassed) {
// Extract summary line for inline detail (e.g., "1 passed (242ms)")
const summaryLine = output
.split("\n")
.find((line: string) => /Test Files\s+.+passed/.test(line));
const summary = summaryLine?.trim() ?? "All tests passed";
ctx.ui.setWidget("test-status", [
`${theme.fg("success", "●")} ${theme.fg("text", summary)}`,
]);
} else if (testsFailed) {
const summaryLine = output
.split("\n")
.find((line: string) => /Tests\s+.+failed/.test(line));
const summary = summaryLine?.trim() ?? "Tests failed";
ctx.ui.setWidget("test-status", [
`${theme.fg("error", "●")} ${theme.fg("text", summary)}`,
]);
}
});
}
@@ -0,0 +1,3 @@
export const isTestCommand = (command: string) => {
return command.includes("npm test") || command.includes("vitest")
}
+22
View File
@@ -0,0 +1,22 @@
{
"name": "pi-test-status",
"version": "0.1.0",
"description": "Pi extension that shows test status (pass/fail) as a widget after running tests",
"keywords": ["pi-package", "pi-extension", "test-status", "vitest"],
"type": "module",
"main": "extensions/index.ts",
"files": [
"extensions",
"README.md"
],
"scripts": {
"test": "echo \"No tests yet\""
},
"peerDependencies": {
"@earendil-works/pi-coding-agent": "*"
},
"pi": {
"extensions": ["./extensions"]
},
"license": "MIT"
}