@anthodev/pi-tools-style 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -4
- package/package.json +11 -6
- package/src/render-decorator.ts +68 -8
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Built-in tools, MCP integrations, AFT, and third-party extensions keep control o
|
|
|
25
25
|
|
|
26
26
|
## Installation
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
This checkout targets Pi `1.0.x`, verified with `1.0.0`, and requires Node.js `>=22.19.0`.
|
|
29
29
|
|
|
30
30
|
To install it, use the following command:
|
|
31
31
|
|
|
@@ -135,7 +135,7 @@ Borders resolve their color from the active Pi theme on every render. Switching
|
|
|
135
135
|
|
|
136
136
|
## How it works
|
|
137
137
|
|
|
138
|
-
Pi `0.
|
|
138
|
+
Pi `1.0.x` does not expose public, composable render middleware covering both tools and direct shell commands. `pi-tools-style` therefore installs a narrow wrapper around the existing `render()` methods of `ToolExecutionComponent` and `BashExecutionComponent`.
|
|
139
139
|
|
|
140
140
|
The wrapper asks the previous renderer to draw at the frame's inner width, removes only paired outer horizontal rules, and then adds ANSI-aware chrome. It does not call `pi.registerTool()`, replace tool definitions, or intercept execution.
|
|
141
141
|
|
|
@@ -143,12 +143,13 @@ Configuration and animation state live behind `Symbol.for()` keys. Reloading upd
|
|
|
143
143
|
|
|
144
144
|
## Compatibility and limits
|
|
145
145
|
|
|
146
|
-
- Pi `0.84.x` is
|
|
146
|
+
- Pi `1.0.x` is the supported target, verified with `1.0.0` in both `fullscreen` and `regular` TUI modes. Pi `0.84.x` is no longer a supported target. Private TUI internals may change in later releases.
|
|
147
147
|
- The render wrapper is necessarily unsupported until Pi exposes public, composable render middleware.
|
|
148
148
|
- Kitty, iTerm2, and Sixel image output remains unboxed by design.
|
|
149
149
|
- Slash commands remain outside the current scope because their transcript output has no uniform component.
|
|
150
150
|
- Another extension can replace this wrapper if it overwrites component rendering without composing with the previous renderer.
|
|
151
151
|
- Unsupported renderer shapes fail open rather than affecting tool execution.
|
|
152
|
+
- Frame characters remain selectable text and are included in copied output. Excluding decorative cells would require Pi/TUI integration for fullscreen selection; no portable exclusion path has been identified for native terminal selection in regular mode.
|
|
152
153
|
|
|
153
154
|
## Development
|
|
154
155
|
|
|
@@ -160,10 +161,36 @@ npm run check
|
|
|
160
161
|
npm pack --dry-run --json
|
|
161
162
|
```
|
|
162
163
|
|
|
163
|
-
`npm run check` runs TypeScript validation and the Vitest suite. Tests cover frame width, ANSI output, terminal images, native and AFT rule removal, category colors, icon modes, persisted settings, spinner lifecycle, real `ToolExecutionComponent` integration,
|
|
164
|
+
`npm run check` runs TypeScript validation and the Vitest suite. Tests cover frame width, ANSI output, terminal images, native and AFT rule removal, category colors, icon modes, persisted settings, spinner lifecycle, real `ToolExecutionComponent` and `BashExecutionComponent` integration, third-party renderer composition, decorator toggling and reinstallation, reload idempotence, fail-open behavior, and the no-`registerTool()` contract.
|
|
165
|
+
|
|
166
|
+
Release-note tests cover exact version sections, Markdown fences and whitespace, duplicate or missing sections, tagged Git snapshots, annotated and lightweight tags, and CLI failure paths.
|
|
164
167
|
|
|
165
168
|
`--legacy-peer-deps` avoids an npm 10.9.8 Arborist failure observed while resolving the development peer dependency graph.
|
|
166
169
|
|
|
170
|
+
## Releases
|
|
171
|
+
|
|
172
|
+
Releases are triggered only by stable `vX.Y.Z` tag pushes. `.github/workflows/publish.yml` validates the tagged commit on Node.js `22.19.0` and `24.x`, packages one npm tarball, publishes it to npm, then creates the GitHub release with that tarball and `SHA256SUMS` as assets.
|
|
173
|
+
|
|
174
|
+
`CHANGELOG.md` is the source of release notes. CI extracts the exact version section from the tagged commit, not the working tree, and preserves its summary, lists, links, and Markdown. Invalid tags, mismatched revisions, or missing, empty, or duplicate sections fail before publication. CI neither generates notes from commit subjects nor edits the changelog.
|
|
175
|
+
|
|
176
|
+
To prepare a release:
|
|
177
|
+
|
|
178
|
+
1. Update `package.json` and regenerate `package-lock.json`. Move the release entries from `Unreleased` into a dated `## [X.Y.Z] - YYYY-MM-DD` section, preserving verified commit or PR references and the full changelog comparison link.
|
|
179
|
+
2. Run `PI_CODING_AGENT_DIR="$(mktemp -d)" npm run check` and `npm run pack:check`.
|
|
180
|
+
3. Seal and push the release-preparation commit on `develop`. With jj, use the sealed commit's explicit SHA, never an empty working-copy `@`.
|
|
181
|
+
4. Set `VERSION` to the prepared package version and `RELEASE_SHA` to that commit's full SHA, then validate the notes before pushing the tag:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
jj tag set "v${VERSION}" -r "$RELEASE_SHA"
|
|
185
|
+
notes="$(mktemp)"
|
|
186
|
+
node scripts/generate-release-notes.ts "v${VERSION}" "$RELEASE_SHA" "$notes"
|
|
187
|
+
jj git push --tag "v${VERSION}"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The workflow requires the tag to match the package and lockfile versions and to belong to `develop`. npm publication uses OIDC Trusted Publishing on a GitHub-hosted runner with the protected `npm` environment. Keep the `publish.yml` filename and environment aligned with the trusted publisher configuration. Concurrent release tags are serialized.
|
|
191
|
+
|
|
192
|
+
Publication is not an idempotent update. An already-published npm version or existing GitHub release makes a rerun fail. npm publication precedes GitHub release creation, so a failure in the latter can leave the package already published. Do not move a published tag or delete a release to make a rerun succeed.
|
|
193
|
+
|
|
167
194
|
## License
|
|
168
195
|
|
|
169
196
|
MIT
|
package/package.json
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anthodev/pi-tools-style",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Presentation-only boxes for Pi tool calls and shell commands",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi-extension",
|
|
8
|
+
"tui",
|
|
9
|
+
"tools"
|
|
10
|
+
],
|
|
6
11
|
"type": "module",
|
|
7
12
|
"license": "MIT",
|
|
8
13
|
"repository": {
|
|
@@ -35,12 +40,12 @@
|
|
|
35
40
|
"access": "public"
|
|
36
41
|
},
|
|
37
42
|
"peerDependencies": {
|
|
38
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
39
|
-
"@earendil-works/pi-tui": "0.
|
|
43
|
+
"@earendil-works/pi-coding-agent": "1.0.x",
|
|
44
|
+
"@earendil-works/pi-tui": "1.0.x"
|
|
40
45
|
},
|
|
41
46
|
"devDependencies": {
|
|
42
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
43
|
-
"@earendil-works/pi-tui": "0.
|
|
47
|
+
"@earendil-works/pi-coding-agent": "1.0.0",
|
|
48
|
+
"@earendil-works/pi-tui": "1.0.0",
|
|
44
49
|
"@types/node": "24.12.0",
|
|
45
50
|
"typescript": "5.9.3",
|
|
46
51
|
"vitest": "4.1.9"
|
package/src/render-decorator.ts
CHANGED
|
@@ -34,6 +34,23 @@ interface StoredConfig extends Omit<RenderDecoratorConfig, "enabled"> {
|
|
|
34
34
|
enabled: boolean;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
interface ResolvedFrameOptions {
|
|
38
|
+
options: FrameOptions;
|
|
39
|
+
styleSignature: string | undefined;
|
|
40
|
+
title: string | undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface RenderCacheEntry {
|
|
44
|
+
config: StoredConfig;
|
|
45
|
+
output: string[];
|
|
46
|
+
rendered: readonly string[];
|
|
47
|
+
styleSignature: string | undefined;
|
|
48
|
+
title: string | undefined;
|
|
49
|
+
width: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const BORDER_STYLE_PROBE = "╭─╮│╰─╯";
|
|
53
|
+
|
|
37
54
|
export function installRenderDecorator<T extends Renderable>(
|
|
38
55
|
component: RenderableConstructor<T>,
|
|
39
56
|
config: RenderDecoratorConfig<T>,
|
|
@@ -96,6 +113,8 @@ function createDecoratedRenderer(
|
|
|
96
113
|
key: symbol,
|
|
97
114
|
renderOriginal: RenderFunction,
|
|
98
115
|
): RenderFunction {
|
|
116
|
+
const cache = new WeakMap<Renderable, RenderCacheEntry>();
|
|
117
|
+
|
|
99
118
|
return function decoratedRender(width: number): string[] {
|
|
100
119
|
const current = readConfig(key);
|
|
101
120
|
if (!current?.enabled) return renderOriginal.call(this, width);
|
|
@@ -109,22 +128,38 @@ function createDecoratedRenderer(
|
|
|
109
128
|
}
|
|
110
129
|
|
|
111
130
|
try {
|
|
112
|
-
|
|
131
|
+
const resolved = resolveFrameOptions(this, current);
|
|
132
|
+
const cached = cache.get(this);
|
|
133
|
+
if (
|
|
134
|
+
cached?.config === current &&
|
|
135
|
+
cached.width === width &&
|
|
136
|
+
cached.title === resolved.title &&
|
|
137
|
+
cached.styleSignature === resolved.styleSignature &&
|
|
138
|
+
linesEqual(cached.rendered, rendered)
|
|
139
|
+
) {
|
|
140
|
+
return cached.output;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const output = decorateLines(rendered, width, current, resolved.options);
|
|
144
|
+
cache.set(this, {
|
|
145
|
+
config: current,
|
|
146
|
+
output,
|
|
147
|
+
rendered: [...rendered],
|
|
148
|
+
styleSignature: resolved.styleSignature,
|
|
149
|
+
title: resolved.title,
|
|
150
|
+
width,
|
|
151
|
+
});
|
|
152
|
+
return output;
|
|
113
153
|
} catch {
|
|
114
154
|
return renderOriginal.call(this, width);
|
|
115
155
|
}
|
|
116
156
|
};
|
|
117
157
|
}
|
|
118
158
|
|
|
119
|
-
function
|
|
159
|
+
function resolveFrameOptions(
|
|
120
160
|
instance: Renderable,
|
|
121
|
-
rendered: readonly string[],
|
|
122
|
-
width: number,
|
|
123
161
|
config: StoredConfig,
|
|
124
|
-
):
|
|
125
|
-
const lines = config.trimOuterRules
|
|
126
|
-
? trimOuterRules(rendered)
|
|
127
|
-
: [...rendered];
|
|
162
|
+
): ResolvedFrameOptions {
|
|
128
163
|
const title = config.title?.(instance);
|
|
129
164
|
const options: FrameOptions = {};
|
|
130
165
|
|
|
@@ -137,9 +172,34 @@ function decorateLines(
|
|
|
137
172
|
}
|
|
138
173
|
if (title) options.title = title;
|
|
139
174
|
|
|
175
|
+
return {
|
|
176
|
+
options,
|
|
177
|
+
// Theme providers can change border output without changing component content.
|
|
178
|
+
styleSignature: styleBorder?.(BORDER_STYLE_PROBE),
|
|
179
|
+
title,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function decorateLines(
|
|
184
|
+
rendered: readonly string[],
|
|
185
|
+
width: number,
|
|
186
|
+
config: StoredConfig,
|
|
187
|
+
options: FrameOptions,
|
|
188
|
+
): string[] {
|
|
189
|
+
const lines = config.trimOuterRules
|
|
190
|
+
? trimOuterRules(rendered)
|
|
191
|
+
: [...rendered];
|
|
192
|
+
|
|
140
193
|
return frameLines(lines, width, options);
|
|
141
194
|
}
|
|
142
195
|
|
|
196
|
+
function linesEqual(left: readonly string[], right: readonly string[]): boolean {
|
|
197
|
+
return (
|
|
198
|
+
left.length === right.length &&
|
|
199
|
+
left.every((line, index) => line === right[index])
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
143
203
|
function storeConfig<T extends Renderable>(
|
|
144
204
|
config: RenderDecoratorConfig<T>,
|
|
145
205
|
): void {
|