@wierdbytes/pi-facelift 0.3.2 → 0.4.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 +44 -0
- package/README.md +56 -1
- package/config.test.ts +133 -0
- package/config.ts +143 -0
- package/edit-line-numbers.test.ts +231 -0
- package/index.ts +579 -2
- package/package.json +2 -2
- package/paths.ts +50 -0
- package/scripts/diff-demo.ts +96 -0
package/paths.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem paths for @wierdbytes/pi-facelift.
|
|
3
|
+
*
|
|
4
|
+
* Per-package state lives under `~/.pi/agent/wierd-facelift/` (or
|
|
5
|
+
* `${PI_AGENT_DIR}/wierd-facelift/` if the env var is set):
|
|
6
|
+
*
|
|
7
|
+
* - `config.json` — user-editable settings (see config.ts). Today
|
|
8
|
+
* this is just the `diffLayout` preference; more knobs (themes,
|
|
9
|
+
* icon mode, max preview lines, etc.) can be added later without
|
|
10
|
+
* changing the directory layout.
|
|
11
|
+
*
|
|
12
|
+
* The directory is created lazily on first write — `ensureFaceliftDir`
|
|
13
|
+
* is the canonical entry point. We never call `mkdirSync` from module
|
|
14
|
+
* load so test runners and unrelated tooling don't accidentally touch
|
|
15
|
+
* the agent dir.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
|
|
22
|
+
const PACKAGE_DIRNAME = "wierd-facelift";
|
|
23
|
+
|
|
24
|
+
/** Resolve the pi agent root (`PI_AGENT_DIR` if set, otherwise `~/.pi/agent`). */
|
|
25
|
+
export function piAgentDir(): string {
|
|
26
|
+
const fromEnv = process.env.PI_AGENT_DIR?.trim();
|
|
27
|
+
if (fromEnv) return fromEnv;
|
|
28
|
+
return join(homedir(), ".pi", "agent");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Per-package state directory: `<piAgentDir>/wierd-facelift`. */
|
|
32
|
+
export function faceliftDir(): string {
|
|
33
|
+
return join(piAgentDir(), PACKAGE_DIRNAME);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Path to the persisted config JSON. */
|
|
37
|
+
export function configPath(): string {
|
|
38
|
+
return join(faceliftDir(), "config.json");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Ensure the package directory exists. Idempotent. Caller should invoke
|
|
43
|
+
* this once before the first config save.
|
|
44
|
+
*/
|
|
45
|
+
export function ensureFaceliftDir(): void {
|
|
46
|
+
const dir = faceliftDir();
|
|
47
|
+
if (!existsSync(dir)) {
|
|
48
|
+
mkdirSync(dir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual smoke test for the new write/edit diff renderer.
|
|
3
|
+
*
|
|
4
|
+
* Renders a representative split-view diff inside an open-right facelift
|
|
5
|
+
* frame so the result can be eyeballed against the original pi-diff
|
|
6
|
+
* screenshot. Not run by CI — invoked manually with:
|
|
7
|
+
*
|
|
8
|
+
* bun run packages/facelift/scripts/diff-demo.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
applyDiffPalette,
|
|
13
|
+
canRenderSplit,
|
|
14
|
+
getDiffLayoutPreference,
|
|
15
|
+
lang,
|
|
16
|
+
parseDiff,
|
|
17
|
+
renderSplit,
|
|
18
|
+
resolveDiffColors,
|
|
19
|
+
summarize,
|
|
20
|
+
type DiffLayout,
|
|
21
|
+
} from "@wierdbytes/pi-common/diff";
|
|
22
|
+
import {
|
|
23
|
+
frameResultWithBottomLabel,
|
|
24
|
+
frameTop,
|
|
25
|
+
} from "@wierdbytes/pi-common/tool-frame";
|
|
26
|
+
|
|
27
|
+
applyDiffPalette();
|
|
28
|
+
|
|
29
|
+
// Minimal theme stub. By default we mimic tokyo-night-ish bg tokens so the
|
|
30
|
+
// auto-derived diff bg tints (BG_DEL/BG_ADD/BG_BASE) blend with each other.
|
|
31
|
+
// Pass FACELIFT_DEMO_PLAIN=1 to fall back to default terminal bg for the
|
|
32
|
+
// uncommon case where you want to inspect raw output.
|
|
33
|
+
const plain = process.env.FACELIFT_DEMO_PLAIN === "1";
|
|
34
|
+
const theme = plain
|
|
35
|
+
? ({
|
|
36
|
+
fg: (_key: string, text: string) => text,
|
|
37
|
+
bold: (text: string) => `\x1b[1m${text}\x1b[22m`,
|
|
38
|
+
getFgAnsi: () => "",
|
|
39
|
+
getBgAnsi: () => "",
|
|
40
|
+
} as unknown as Parameters<typeof frameTop>[2])
|
|
41
|
+
: ({
|
|
42
|
+
fg: (_key: string, text: string) => text,
|
|
43
|
+
bold: (text: string) => `\x1b[1m${text}\x1b[22m`,
|
|
44
|
+
getFgAnsi: (key: string) => {
|
|
45
|
+
if (key === "toolDiffAdded") return "\x1b[38;2;100;180;120m"; // green
|
|
46
|
+
if (key === "toolDiffRemoved") return "\x1b[38;2;200;100;100m"; // red
|
|
47
|
+
return "";
|
|
48
|
+
},
|
|
49
|
+
getBgAnsi: (key: string) => {
|
|
50
|
+
if (key === "toolSuccessBg") return "\x1b[48;2;26;30;46m"; // tokyo-night-ish slate
|
|
51
|
+
if (key === "toolErrorBg") return "\x1b[48;2;46;26;30m";
|
|
52
|
+
return "";
|
|
53
|
+
},
|
|
54
|
+
} as unknown as Parameters<typeof frameTop>[2]);
|
|
55
|
+
|
|
56
|
+
const oldText = `if len(blockers) == 0 {
|
|
57
|
+
return nil
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
tx, err := s.db.BeginTx(ctx, nil)
|
|
61
|
+
if err != nil {
|
|
62
|
+
return fmt.Errorf("begin tx: %w", err)
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
const newText = `if len(blockers) == 0 {
|
|
66
|
+
return nil
|
|
67
|
+
}
|
|
68
|
+
return retryOnBusy(ctx, func() error { return s.blockOnce(ctx, id, blockers) })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
func (s *Store) blockOnce(ctx context.Context, id string, blockers []string) error {
|
|
72
|
+
tx, err := s.db.BeginTx(ctx, nil)
|
|
73
|
+
if err != nil {
|
|
74
|
+
return fmt.Errorf("begin tx: %w", err)
|
|
75
|
+
`;
|
|
76
|
+
|
|
77
|
+
const diff = parseDiff(oldText, newText);
|
|
78
|
+
const colors = resolveDiffColors(theme as never);
|
|
79
|
+
const width = Number(process.env.COLUMNS) || 200;
|
|
80
|
+
const innerW = width - 1;
|
|
81
|
+
|
|
82
|
+
// Mirror facelift's `decideDiffLayout` so the demo also respects
|
|
83
|
+
// `DIFF_LAYOUT=...` / `diffLayout` settings when invoked manually.
|
|
84
|
+
const pref = getDiffLayoutPreference();
|
|
85
|
+
let layout: DiffLayout | undefined;
|
|
86
|
+
if (pref === "split") layout = "split";
|
|
87
|
+
else if (pref === "unified") layout = "unified";
|
|
88
|
+
else if (pref === "per-edit") layout = undefined;
|
|
89
|
+
else layout = canRenderSplit(diff, innerW, 60) ? "split" : "unified";
|
|
90
|
+
|
|
91
|
+
const title = `\x1b[1medit\x1b[22m internal/store/doltlite/deps.go`;
|
|
92
|
+
console.log(frameTop(title, "success", theme, width));
|
|
93
|
+
|
|
94
|
+
const body = await renderSplit(diff, lang("foo.go"), 60, colors, innerW, { frameless: true, layout });
|
|
95
|
+
const label = `1 edit ${summarize(diff.added, diff.removed)} (${diff.lines.length} diff lines)`;
|
|
96
|
+
console.log(frameResultWithBottomLabel(body, label, "success", theme, width));
|