pi-startup-redraw-fix 0.1.13 → 0.2.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.0] - 2026-07-03
6
+
7
+ ### Added
8
+ - Added an `enabled` master toggle (defaults to `true`) that gates extension startup, and structured debug logging written to an extension-local `debug/` directory when `debug` is enabled. ([2d57235](https://github.com/MasuRii/pi-startup-redraw-fix/commit/2d57235b6b72b6ec28f0a5298228d8fafe828da5))
9
+
10
+ ### Changed
11
+ - Widened Pi coding-agent and Pi TUI peer dependency ranges through `^0.80.0` and added a `postinstall` patch with npm `overrides` to resolve known vulnerabilities in transitive dependencies. ([2f1f400](https://github.com/MasuRii/pi-startup-redraw-fix/commit/2f1f400f804ee08b8f72f0f5d43ee6194de9b500))
12
+ - Extracted a fresh-terminal fixture helper for the terminal clear-sequence tests. ([0fc323d](https://github.com/MasuRii/pi-startup-redraw-fix/commit/0fc323d19a74ed9e7cdf070cb57015261e22865f))
13
+ - Updated README badge styling and added a ko-fi support button. ([0c3f0c3](https://github.com/MasuRii/pi-startup-redraw-fix/commit/0c3f0c36291a637dc47f2a5e8335eeb152e26244))
14
+
5
15
  ## [0.1.13] - 2026-06-16
6
16
 
7
17
  ### Fixed
package/README.md CHANGED
@@ -1,9 +1,17 @@
1
+ <div align="center">
2
+
1
3
  # pi-startup-redraw-fix
2
4
 
3
- A Pi coding agent extension that patches terminal full-clear escape sequence ordering to prevent startup redraw glitches in certain terminal emulators.
5
+ [![npm version](https://img.shields.io/npm/v/pi-startup-redraw-fix?style=for-the-badge)](https://www.npmjs.com/package/pi-startup-redraw-fix)
6
+ [![License](https://img.shields.io/github/license/MasuRii/pi-startup-redraw-fix?style=for-the-badge)](LICENSE)
7
+ [![Platform](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux%20%7C%20Windows-blue?style=for-the-badge)]()
4
8
 
9
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Y8Y01PSSVR)
10
+
11
+ A Pi coding agent extension that patches terminal full-clear escape sequence ordering to prevent startup redraw glitches in certain terminal emulators.
5
12
  <img width="1360" height="752" alt="image" src="https://github.com/user-attachments/assets/05bfd443-052c-475e-bc80-3350cde5c642" />
6
13
 
14
+ </div>
7
15
 
8
16
  ## Table of Contents
9
17
 
@@ -1,3 +1,4 @@
1
- {
2
- "enabled": true
3
- }
1
+ {
2
+ "enabled": true,
3
+ "debug": false
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-startup-redraw-fix",
3
- "version": "0.1.13",
3
+ "version": "0.2.0",
4
4
  "description": "Pi extension that patches terminal full-clear ordering to avoid startup redraw glitches.",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -20,7 +20,8 @@
20
20
  "build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
21
21
  "lint": "npm run build",
22
22
  "test": "npx --yes tsx --test normalize-clear-sequence.test.mjs",
23
- "check": "npm run lint && npm run test"
23
+ "check": "npm run lint && npm run test",
24
+ "postinstall": "node -e \"const fs=require('fs'),cp=require('child_process'),p=require('path');const cwd=process.cwd();const normalized=cwd.split(p.sep).join('/');if(!normalized.includes('/.pi/agent/extensions/'))process.exit(0);const s=p.resolve(cwd,'../../scripts/patch-vulnerable-deps.mjs');if(!fs.existsSync(s))process.exit(0);const r=cp.spawnSync(process.execPath,[s,'--target',cwd,'--quiet'],{stdio:'inherit'});process.exit(r.status||0)\""
24
25
  },
25
26
  "keywords": [
26
27
  "pi-package",
@@ -58,7 +59,14 @@
58
59
  ]
59
60
  },
60
61
  "peerDependencies": {
61
- "@earendil-works/pi-coding-agent": "^0.74.0 || ^0.75.0 || ^0.77.0 || ^0.78.0 || ^0.79.0",
62
- "@earendil-works/pi-tui": "^0.74.0 || ^0.75.0 || ^0.77.0 || ^0.78.0 || ^0.79.0"
62
+ "@earendil-works/pi-coding-agent": "^0.74.0 || ^0.75.0 || ^0.77.0 || ^0.78.0 || ^0.79.0 || ^0.80.0",
63
+ "@earendil-works/pi-tui": "^0.74.0 || ^0.75.0 || ^0.77.0 || ^0.78.0 || ^0.79.0 || ^0.80.0"
64
+ },
65
+ "overrides": {
66
+ "protobufjs": "7.6.3",
67
+ "ws": "8.21.0"
68
+ },
69
+ "devDependencies": {
70
+ "@earendil-works/pi-coding-agent": "^0.80.3"
63
71
  }
64
72
  }
package/src/config.ts ADDED
@@ -0,0 +1,66 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ export interface StartupRedrawFixConfig {
6
+ enabled: boolean;
7
+ debug: boolean;
8
+ }
9
+
10
+ const DEFAULT_CONFIG: StartupRedrawFixConfig = {
11
+ enabled: true,
12
+ debug: false,
13
+ };
14
+
15
+ function resolveExtensionRoot(moduleUrl = import.meta.url): string {
16
+ return join(dirname(fileURLToPath(moduleUrl)), "..");
17
+ }
18
+
19
+ export const EXTENSION_ROOT = resolveExtensionRoot();
20
+ export const CONFIG_PATH = join(EXTENSION_ROOT, "config.json");
21
+ export const DEBUG_DIR = join(EXTENSION_ROOT, "debug");
22
+ export const DEBUG_LOG_PATH = join(DEBUG_DIR, "debug.log");
23
+
24
+ type ReadConfigResult =
25
+ | { ok: true; record: Record<string, unknown> }
26
+ | { ok: false; reason: "missing" | "unparseable" | "not-object" };
27
+
28
+ /**
29
+ * Reads and validates the config file as a plain object.
30
+ *
31
+ * Returns a structured result so callers can react to each failure mode
32
+ * (missing, unparseable, non-object) without silently swallowing errors.
33
+ */
34
+ function readConfigRecord(configPath: string): ReadConfigResult {
35
+ if (!existsSync(configPath)) {
36
+ return { ok: false, reason: "missing" };
37
+ }
38
+
39
+ try {
40
+ const raw = readFileSync(configPath, "utf8");
41
+ const parsed = JSON.parse(raw) as unknown;
42
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
43
+ return { ok: false, reason: "not-object" };
44
+ }
45
+ return { ok: true, record: parsed as Record<string, unknown> };
46
+ } catch {
47
+ return { ok: false, reason: "unparseable" };
48
+ }
49
+ }
50
+
51
+ function readBoolean(record: Record<string, unknown>, key: keyof StartupRedrawFixConfig): boolean {
52
+ const value = record[key];
53
+ return typeof value === "boolean" ? value : DEFAULT_CONFIG[key];
54
+ }
55
+
56
+ export function loadStartupRedrawFixConfig(): StartupRedrawFixConfig {
57
+ const result = readConfigRecord(CONFIG_PATH);
58
+ if (!result.ok) {
59
+ return { ...DEFAULT_CONFIG };
60
+ }
61
+
62
+ return {
63
+ enabled: readBoolean(result.record, "enabled"),
64
+ debug: readBoolean(result.record, "debug"),
65
+ };
66
+ }
package/src/index.ts CHANGED
@@ -1,14 +1,28 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
 
3
+ import { loadStartupRedrawFixConfig } from "./config.js";
4
+ import { createStartupRedrawFixLogger } from "./logging.js";
3
5
  import { applyTerminalClearSequencePatch } from "./terminal-clear-patch.js";
4
6
 
5
7
  export default function startupRedrawFixExtension(pi: ExtensionAPI): void {
8
+ const config = loadStartupRedrawFixConfig();
9
+ if (!config.enabled) {
10
+ return;
11
+ }
12
+
13
+ const logger = createStartupRedrawFixLogger({ getConfig: () => config });
14
+
6
15
  // The terminal clear-sequence patch must be installed before the first
7
16
  // full-clear write at startup, so it is applied synchronously at registration
8
17
  // rather than deferred. The local import graph is tiny (a few dozen lines) and
9
18
  // the only package dependency, pi-tui, is already loaded by Pi core, so there
10
19
  // is no meaningful startup transpile cost to defer here.
11
20
  const patchResult = applyTerminalClearSequencePatch();
21
+ logger.debug("patch.applied", {
22
+ patched: patchResult.patched,
23
+ alreadyPatched: patchResult.alreadyPatched,
24
+ error: patchResult.error,
25
+ });
12
26
 
13
27
  pi.on("session_start", async (_event, ctx) => {
14
28
  if (!ctx.hasUI) {
package/src/logging.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Debug logging for pi-startup-redraw-fix.
3
+ *
4
+ * Appends debug output to a file inside the colocated `debug/` directory
5
+ * when `debug` is true. Creates the directory at runtime only when debug
6
+ * logging is enabled. No console or stdio output.
7
+ */
8
+ import { mkdirSync } from "node:fs";
9
+ import { appendFile } from "node:fs/promises";
10
+
11
+ import { DEBUG_DIR, DEBUG_LOG_PATH } from "./config.js";
12
+
13
+ export interface StartupRedrawFixLogger {
14
+ /** Log a debug event with optional details. No-op when debug is false. */
15
+ debug: (event: string, details?: Record<string, unknown>) => void;
16
+ /** Wait for all queued debug writes to complete. */
17
+ flush: () => Promise<void>;
18
+ }
19
+
20
+ interface LoggerOptions {
21
+ /** Function returning the current config (checked on each call for live debug state). */
22
+ getConfig: () => { debug: boolean };
23
+ /** Override path for the debug log file. */
24
+ debugLogPath?: string;
25
+ /** Override path for the debug directory. */
26
+ debugDir?: string;
27
+ }
28
+
29
+ /**
30
+ * Creates a debug logger that writes to a colocated debug/ directory.
31
+ *
32
+ * - Debug output is only written when `config.debug` is true.
33
+ * - The debug/ directory is created at runtime only when debug logging is enabled.
34
+ * - All writes are async and queued to avoid blocking the extension.
35
+ * - No output is written to console, stdout, or stderr.
36
+ */
37
+ export function createStartupRedrawFixLogger(options: LoggerOptions): StartupRedrawFixLogger {
38
+ const logPath = options.debugLogPath ?? DEBUG_LOG_PATH;
39
+ const logDir = options.debugDir ?? DEBUG_DIR;
40
+ let writeQueue: Promise<void> = Promise.resolve();
41
+
42
+ const enqueueAppend = (line: string): void => {
43
+ writeQueue = writeQueue.then(
44
+ () => appendFile(logPath, `${line}\n`, "utf8"),
45
+ () => appendFile(logPath, `${line}\n`, "utf8"),
46
+ );
47
+ void writeQueue.catch(() => {
48
+ // Logging must never write to stdout/stderr or interrupt extension operation.
49
+ });
50
+ };
51
+
52
+ const debug = (event: string, details: Record<string, unknown> = {}): void => {
53
+ if (!options.getConfig().debug) {
54
+ return;
55
+ }
56
+
57
+ try {
58
+ mkdirSync(logDir, { recursive: true });
59
+ } catch {
60
+ // Cannot create debug directory — skip logging without disrupting startup.
61
+ return;
62
+ }
63
+
64
+ const line = JSON.stringify({
65
+ timestamp: new Date().toISOString(),
66
+ extension: "pi-startup-redraw-fix",
67
+ event,
68
+ ...details,
69
+ });
70
+ enqueueAppend(line);
71
+ };
72
+
73
+ const flush = (): Promise<void> => writeQueue.catch(() => undefined);
74
+
75
+ return { debug, flush };
76
+ }