xtrm-tools 2.4.4 → 2.4.6

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.4.4",
3
+ "version": "2.4.6",
4
4
  "description": "Claude Code tools installer (skills, hooks, MCP servers)",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",
package/config/hooks.json CHANGED
@@ -16,16 +16,6 @@
16
16
  }
17
17
  ],
18
18
  "PreToolUse": [
19
- {
20
- "matcher": "Write|Edit|MultiEdit|mcp__serena__rename_symbol|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol",
21
- "script": "main-guard.mjs",
22
- "timeout": 5000
23
- },
24
- {
25
- "matcher": "Bash",
26
- "script": "main-guard.mjs",
27
- "timeout": 5000
28
- },
29
19
  {
30
20
  "matcher": "Edit|Write|MultiEdit|NotebookEdit|mcp__serena__rename_symbol|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol",
31
21
  "script": "beads-edit-gate.mjs",
@@ -43,11 +33,6 @@
43
33
  "script": "beads-claim-sync.mjs",
44
34
  "timeout": 5000
45
35
  },
46
- {
47
- "matcher": "Bash",
48
- "script": "main-guard-post-push.mjs",
49
- "timeout": 5000
50
- },
51
36
  {
52
37
  "matcher": "Write|Edit|MultiEdit|mcp__serena__rename_symbol|mcp__serena__replace_symbol_body|mcp__serena__insert_after_symbol|mcp__serena__insert_before_symbol",
53
38
  "script": "quality-check.cjs",
@@ -0,0 +1,201 @@
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
+ import {
3
+ createBashTool,
4
+ createEditTool,
5
+ createFindTool,
6
+ createGrepTool,
7
+ createLsTool,
8
+ createReadTool,
9
+ createWriteTool,
10
+ } from "@mariozechner/pi-coding-agent";
11
+ import { Text } from "@mariozechner/pi-tui";
12
+
13
+ function getTextContent(result: any): string {
14
+ if (!result?.content || !Array.isArray(result.content)) return "";
15
+ return result.content
16
+ .filter((c: any) => c?.type === "text" && typeof c.text === "string")
17
+ .map((c: any) => c.text)
18
+ .join("\n")
19
+ .trim();
20
+ }
21
+
22
+ function oneLine(s: string): string {
23
+ return (s || "").replace(/\s+/g, " ").trim();
24
+ }
25
+
26
+ function summarize(result: any): { text: string; isError: boolean } {
27
+ const raw = getTextContent(result);
28
+ if (!raw) return { text: "done", isError: false };
29
+ const line = oneLine(raw.split("\n").find((l) => l.trim()) || "");
30
+ const lower = line.toLowerCase();
31
+ const isError = lower.includes("error") || lower.includes("failed") || lower.includes("exception");
32
+ return { text: line.slice(0, 140) || "done", isError };
33
+ }
34
+
35
+ const toolCache = new Map<string, ReturnType<typeof createBuiltInTools>>();
36
+ function createBuiltInTools(cwd: string) {
37
+ return {
38
+ read: createReadTool(cwd),
39
+ bash: createBashTool(cwd),
40
+ edit: createEditTool(cwd),
41
+ write: createWriteTool(cwd),
42
+ find: createFindTool(cwd),
43
+ grep: createGrepTool(cwd),
44
+ ls: createLsTool(cwd),
45
+ };
46
+ }
47
+ function getBuiltInTools(cwd: string) {
48
+ let tools = toolCache.get(cwd);
49
+ if (!tools) {
50
+ tools = createBuiltInTools(cwd);
51
+ toolCache.set(cwd, tools);
52
+ }
53
+ return tools;
54
+ }
55
+
56
+ export default function (pi: ExtensionAPI) {
57
+ let minimalEnabled = true;
58
+ let thinkingStatusEnabled = true;
59
+ let spinnerTimer: NodeJS.Timeout | null = null;
60
+ let spinnerIndex = 0;
61
+ const frames = ["thinking ", "thinking. ", "thinking.. ", "thinking..."];
62
+
63
+ const clearSpinner = (ctx: any) => {
64
+ if (spinnerTimer) {
65
+ clearInterval(spinnerTimer);
66
+ spinnerTimer = null;
67
+ }
68
+ if (ctx?.hasUI) {
69
+ ctx.ui.setStatus("thinking", undefined);
70
+ ctx.ui.setHeader(undefined);
71
+ }
72
+ };
73
+
74
+ const mountThinkingHeader = (ctx: any) => {
75
+ if (!ctx?.hasUI) return;
76
+ ctx.ui.setHeader((_tui: any, theme: any) => ({
77
+ invalidate() {},
78
+ render(width: number): string[] {
79
+ const text = frames[spinnerIndex];
80
+ return [oneLine(theme.fg("accent", text)).slice(0, width)];
81
+ },
82
+ }));
83
+ };
84
+
85
+ const startSpinner = (ctx: any) => {
86
+ if (!thinkingStatusEnabled || !ctx?.hasUI) return;
87
+ clearSpinner(ctx);
88
+ spinnerIndex = 0;
89
+ ctx.ui.setStatus("thinking", frames[spinnerIndex]);
90
+ mountThinkingHeader(ctx);
91
+ spinnerTimer = setInterval(() => {
92
+ spinnerIndex = (spinnerIndex + 1) % frames.length;
93
+ ctx.ui.setStatus("thinking", frames[spinnerIndex]);
94
+ mountThinkingHeader(ctx);
95
+ }, 220);
96
+ };
97
+
98
+ const renderCollapsedResult = (result: any, theme: any) => {
99
+ const s = summarize(result);
100
+ const icon = s.isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
101
+ const text = s.isError ? theme.fg("error", s.text) : theme.fg("muted", s.text);
102
+ if (minimalEnabled) return new Text(` ${icon} ${text}`, 0, 0);
103
+ return new Text(theme.fg("muted", ` → ${s.text}`), 0, 0);
104
+ };
105
+
106
+ const renderExpandedResult = (result: any, theme: any) => {
107
+ const text = getTextContent(result);
108
+ if (!text) return new Text("", 0, 0);
109
+ const output = text.split("\n").map((line) => theme.fg("toolOutput", line)).join("\n");
110
+ return new Text(`\n${output}`, 0, 0);
111
+ };
112
+
113
+ pi.registerTool({
114
+ name: "bash",
115
+ label: "bash",
116
+ description: getBuiltInTools(process.cwd()).bash.description,
117
+ parameters: getBuiltInTools(process.cwd()).bash.parameters,
118
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
119
+ return getBuiltInTools(ctx.cwd).bash.execute(toolCallId, params, signal, onUpdate);
120
+ },
121
+ renderCall(args, theme) {
122
+ const cmd = oneLine(args.command || "");
123
+ return new Text(`${theme.fg("toolTitle", theme.bold("bash"))}(${theme.fg("accent", cmd || "...")})`, 0, 0);
124
+ },
125
+ renderResult(result, { expanded }, theme) {
126
+ return expanded ? renderExpandedResult(result, theme) : renderCollapsedResult(result, theme);
127
+ },
128
+ });
129
+
130
+ for (const name of ["read", "write", "edit", "find", "grep", "ls"] as const) {
131
+ pi.registerTool({
132
+ name,
133
+ label: name,
134
+ description: (getBuiltInTools(process.cwd()) as any)[name].description,
135
+ parameters: (getBuiltInTools(process.cwd()) as any)[name].parameters,
136
+ async execute(toolCallId, params, signal, onUpdate, ctx) {
137
+ return (getBuiltInTools(ctx.cwd) as any)[name].execute(toolCallId, params, signal, onUpdate);
138
+ },
139
+ renderCall(args, theme) {
140
+ const suffix = oneLine(args.path || args.pattern || "");
141
+ return new Text(`${theme.fg("toolTitle", theme.bold(name))}${suffix ? `(${theme.fg("accent", suffix)})` : ""}`, 0, 0);
142
+ },
143
+ renderResult(result, { expanded }, theme) {
144
+ return expanded ? renderExpandedResult(result, theme) : renderCollapsedResult(result, theme);
145
+ },
146
+ });
147
+ }
148
+
149
+ pi.registerCommand("minimal-on", {
150
+ description: "Enable minimal collapsed tool output",
151
+ handler: async (_args, ctx) => {
152
+ minimalEnabled = true;
153
+ ctx.ui.notify("Minimal mode enabled", "info");
154
+ },
155
+ });
156
+
157
+ pi.registerCommand("minimal-off", {
158
+ description: "Disable minimal collapsed tool output",
159
+ handler: async (_args, ctx) => {
160
+ minimalEnabled = false;
161
+ ctx.ui.notify("Minimal mode disabled", "info");
162
+ },
163
+ });
164
+
165
+ pi.registerCommand("minimal-toggle", {
166
+ description: "Toggle minimal collapsed tool output",
167
+ handler: async (_args, ctx) => {
168
+ minimalEnabled = !minimalEnabled;
169
+ ctx.ui.notify(`Minimal mode ${minimalEnabled ? "enabled" : "disabled"}`, "info");
170
+ },
171
+ });
172
+
173
+ pi.registerCommand("thinking-status-toggle", {
174
+ description: "Toggle flashing thinking status indicator",
175
+ handler: async (_args, ctx) => {
176
+ thinkingStatusEnabled = !thinkingStatusEnabled;
177
+ if (!thinkingStatusEnabled) clearSpinner(ctx);
178
+ ctx.ui.notify(`Thinking status ${thinkingStatusEnabled ? "enabled" : "disabled"}`, "info");
179
+ },
180
+ });
181
+
182
+ pi.on("turn_start", async (_event, ctx) => {
183
+ startSpinner(ctx);
184
+ return undefined;
185
+ });
186
+
187
+ pi.on("turn_end", async (_event, ctx) => {
188
+ clearSpinner(ctx);
189
+ return undefined;
190
+ });
191
+
192
+ pi.on("agent_end", async (_event, ctx) => {
193
+ clearSpinner(ctx);
194
+ return undefined;
195
+ });
196
+
197
+ pi.on("session_shutdown", async (_event, ctx) => {
198
+ clearSpinner(ctx);
199
+ return undefined;
200
+ });
201
+ }
@@ -12,5 +12,5 @@
12
12
  ],
13
13
  "steeringMode": "all",
14
14
  "followUpMode": "all",
15
- "hideThinkingBlock": false
15
+ "hideThinkingBlock": true
16
16
  }
@@ -5,9 +5,7 @@
5
5
  // Output: { hookSpecificOutput: { additionalSystemPrompt } }
6
6
 
7
7
  import { execSync } from 'node:child_process';
8
- import { readFileSync, existsSync } from 'node:fs';
9
- import { join } from 'node:path';
10
- import { resolveSessionId } from './beads-gate-utils.mjs';
8
+ import { readFileSync } from 'node:fs';
11
9
 
12
10
  function readInput() {
13
11
  try { return JSON.parse(readFileSync(0, 'utf-8')); } catch { return null; }
@@ -17,36 +15,25 @@ function getBranch(cwd) {
17
15
  try {
18
16
  return execSync('git branch --show-current', {
19
17
  encoding: 'utf8', cwd,
20
- stdio: ['pipe', 'pipe', 'pipe'], timeout: 3000,
18
+ stdio: ['pipe', 'pipe', 'pipe'], timeout: 2000,
21
19
  }).trim() || null;
22
20
  } catch { return null; }
23
21
  }
24
22
 
25
- function getSessionClaim(sessionId, cwd) {
26
- try {
27
- const out = execSync(`bd kv get "claimed:${sessionId}"`, {
28
- encoding: 'utf8', cwd,
29
- stdio: ['pipe', 'pipe', 'pipe'], timeout: 3000,
30
- }).trim();
31
- return out || null;
32
- } catch { return null; }
33
- }
23
+ try {
24
+ const input = readInput();
25
+ if (!input) process.exit(0);
34
26
 
35
- const input = readInput();
36
- if (!input) process.exit(0);
27
+ const cwd = input.cwd || process.cwd();
28
+ const branch = getBranch(cwd);
37
29
 
38
- const cwd = input.cwd || process.cwd();
39
- const sessionId = resolveSessionId(input);
40
- const branch = getBranch(cwd);
41
- const isBeads = existsSync(join(cwd, '.beads'));
42
- const claim = isBeads && sessionId ? getSessionClaim(sessionId, cwd) : null;
30
+ if (!branch) process.exit(0);
43
31
 
44
- if (!branch && !claim) process.exit(0);
45
-
46
- const context = `[Context: branch=${branch ?? 'unknown'}${claim ? ', claim=' + claim : ''}]`;
47
-
48
- process.stdout.write(JSON.stringify({
49
- hookSpecificOutput: { additionalSystemPrompt: context },
50
- }));
51
- process.stdout.write('\n');
52
- process.exit(0);
32
+ process.stdout.write(JSON.stringify({
33
+ hookSpecificOutput: { additionalSystemPrompt: `[Context: branch=${branch}]` },
34
+ }));
35
+ process.stdout.write('\n');
36
+ process.exit(0);
37
+ } catch {
38
+ process.exit(0);
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xtrm-tools",
3
- "version": "2.4.4",
3
+ "version": "2.4.6",
4
4
  "description": "Claude Code tools installer (skills, hooks, MCP servers)",
5
5
  "license": "MIT",
6
6
  "type": "module",