xtrm-tools 2.1.23 → 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xtrm-cli",
3
- "version": "2.1.23",
3
+ "version": "2.1.25",
4
4
  "description": "Claude Code tools installer (skills, hooks, MCP servers)",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",
@@ -71,7 +71,9 @@ export default function (pi: ExtensionAPI) {
71
71
  if (isBashToolResult(event)) {
72
72
  const command = event.input.command;
73
73
 
74
- if (command && /\bbd\s+update\b/.test(command) && /--claim\b/.test(command) && !event.isError) {
74
+ // Auto-claim on bd update --claim regardless of exit code.
75
+ // bd returns exit 1 with "already in_progress" when status unchanged — still a valid claim intent.
76
+ if (command && /\bbd\s+update\b/.test(command) && /--claim\b/.test(command)) {
75
77
  const issueMatch = command.match(/\bbd\s+update\s+(\S+)/);
76
78
  if (issueMatch) {
77
79
  const issueId = issueMatch[1];
@@ -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
- export default function (pi: ExtensionAPI) {
11
- let turnCount = 0;
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
- const brand = theme.fg("accent", "XTRM");
22
- const turns = theme.fg("dim", `[Turn ${turnCount}]`);
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 [Turn 1] | model | 10% | ⌂ dir | ⎇ branch
42
- const leftParts = [`${brand} ${turns}`, modelStr, usageStr, cwdStr];
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xtrm-tools",
3
- "version": "2.1.23",
3
+ "version": "2.1.25",
4
4
  "description": "Claude Code tools installer (skills, hooks, MCP servers)",
5
5
  "license": "MIT",
6
6
  "type": "module",