xtrm-tools 2.1.24 → 2.1.25
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/cli/package.json
CHANGED
|
@@ -7,19 +7,99 @@
|
|
|
7
7
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
8
8
|
import { truncateToWidth } from "@mariozechner/pi-tui";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import { SubprocessRunner } from "./core/lib";
|
|
12
13
|
|
|
14
|
+
export default function (pi: ExtensionAPI) {
|
|
13
15
|
pi.on("session_start", async (_event, ctx) => {
|
|
16
|
+
const getCwd = () => (ctx as any).cwd || process.cwd();
|
|
17
|
+
const isBeadsProject = (cwd: string) => fs.existsSync(path.join(cwd, ".beads"));
|
|
18
|
+
const getShortId = (id: string) => id.split("-").pop() ?? id;
|
|
19
|
+
|
|
20
|
+
const STATUS_ICONS: Record<string, string> = {
|
|
21
|
+
open: "○",
|
|
22
|
+
in_progress: "◐",
|
|
23
|
+
blocked: "●",
|
|
24
|
+
closed: "✓",
|
|
25
|
+
};
|
|
26
|
+
const STATUS_BG: Record<string, string> = {
|
|
27
|
+
open: "\x1b[48;5;238m",
|
|
28
|
+
in_progress: "\x1b[48;5;28m",
|
|
29
|
+
blocked: "\x1b[48;5;88m",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
interface BeadState {
|
|
33
|
+
claimId: string | null;
|
|
34
|
+
shortId: string | null;
|
|
35
|
+
status: string | null;
|
|
36
|
+
openCount: number;
|
|
37
|
+
lastFetch: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let beadState: BeadState = { claimId: null, shortId: null, status: null, openCount: 0, lastFetch: 0 };
|
|
41
|
+
let refreshing = false;
|
|
42
|
+
const CACHE_TTL = 5000;
|
|
43
|
+
|
|
44
|
+
const refreshBeadState = async () => {
|
|
45
|
+
if (refreshing || Date.now() - beadState.lastFetch < CACHE_TTL) return;
|
|
46
|
+
const cwd = getCwd();
|
|
47
|
+
if (!isBeadsProject(cwd)) return;
|
|
48
|
+
refreshing = true;
|
|
49
|
+
try {
|
|
50
|
+
const sessionId = ctx.sessionManager.sessionId;
|
|
51
|
+
|
|
52
|
+
const claimResult = await SubprocessRunner.run("bd", ["kv", "get", `claimed:${sessionId}`], { cwd });
|
|
53
|
+
const claimId = claimResult.code === 0 ? claimResult.stdout.trim() || null : null;
|
|
54
|
+
|
|
55
|
+
let status: string | null = null;
|
|
56
|
+
if (claimId) {
|
|
57
|
+
const showResult = await SubprocessRunner.run("bd", ["show", claimId, "--json"], { cwd });
|
|
58
|
+
if (showResult.code === 0) {
|
|
59
|
+
try { status = JSON.parse(showResult.stdout).status ?? null; } catch {}
|
|
60
|
+
}
|
|
61
|
+
if (status === "closed") {
|
|
62
|
+
await SubprocessRunner.run("bd", ["kv", "clear", `claimed:${sessionId}`], { cwd });
|
|
63
|
+
beadState = { claimId: null, shortId: null, status: null, openCount: beadState.openCount, lastFetch: Date.now() };
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let openCount = 0;
|
|
69
|
+
const listResult = await SubprocessRunner.run("bd", ["list"], { cwd });
|
|
70
|
+
if (listResult.code === 0) {
|
|
71
|
+
const m = listResult.stdout.match(/\((\d+)\s+open/);
|
|
72
|
+
if (m) openCount = parseInt(m[1], 10);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
beadState = { claimId, shortId: claimId ? getShortId(claimId) : null, status, openCount, lastFetch: Date.now() };
|
|
76
|
+
} catch {}
|
|
77
|
+
finally { refreshing = false; }
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const buildBeadChip = (): string => {
|
|
81
|
+
const { claimId, shortId, status, openCount } = beadState;
|
|
82
|
+
if (claimId && shortId && status) {
|
|
83
|
+
const icon = STATUS_ICONS[status] ?? "?";
|
|
84
|
+
const bg = STATUS_BG[status] ?? "\x1b[48;5;238m";
|
|
85
|
+
return `${bg}\x1b[38;5;15m bd:${shortId}${icon} \x1b[0m`;
|
|
86
|
+
}
|
|
87
|
+
if (openCount > 0) {
|
|
88
|
+
return `\x1b[48;5;238m\x1b[38;5;15m bd:${openCount}${STATUS_ICONS.open} \x1b[0m`;
|
|
89
|
+
}
|
|
90
|
+
return "";
|
|
91
|
+
};
|
|
92
|
+
|
|
14
93
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
15
94
|
const unsub = footerData.onBranchChange(() => tui.requestRender());
|
|
16
|
-
|
|
95
|
+
|
|
17
96
|
return {
|
|
18
97
|
dispose() { unsub(); },
|
|
19
98
|
invalidate() {},
|
|
20
99
|
render(width: number): string[] {
|
|
21
|
-
|
|
22
|
-
|
|
100
|
+
refreshBeadState().catch(() => {});
|
|
101
|
+
|
|
102
|
+
const brand = "\x1b[1m" + theme.fg("accent", "XTRM") + "\x1b[22m";
|
|
23
103
|
|
|
24
104
|
const usage = ctx.getContextUsage();
|
|
25
105
|
const pct = usage?.percent ?? 0;
|
|
@@ -37,25 +117,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
37
117
|
const modelStr = theme.fg("accent", modelId);
|
|
38
118
|
|
|
39
119
|
const sep = theme.fg("dim", " | ");
|
|
40
|
-
|
|
41
|
-
// Layout: XTRM
|
|
42
|
-
const leftParts = [
|
|
120
|
+
|
|
121
|
+
// Layout: XTRM | model | 10% | ⌂ dir | ⎇ branch | bd:xxx◐
|
|
122
|
+
const leftParts = [brand, modelStr, usageStr, cwdStr];
|
|
43
123
|
if (branchStr) leftParts.push(branchStr);
|
|
44
|
-
|
|
124
|
+
|
|
125
|
+
const beadChip = buildBeadChip();
|
|
126
|
+
if (beadChip) leftParts.push(beadChip);
|
|
127
|
+
|
|
45
128
|
const left = leftParts.join(sep);
|
|
46
129
|
return [truncateToWidth(left, width)];
|
|
47
130
|
},
|
|
48
131
|
};
|
|
49
132
|
});
|
|
50
133
|
});
|
|
51
|
-
|
|
52
|
-
pi.on("turn_start", async () => {
|
|
53
|
-
turnCount++;
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
pi.on("session_switch", async (event, _ctx) => {
|
|
57
|
-
if (event.reason === "new") {
|
|
58
|
-
turnCount = 0;
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
134
|
}
|