xtrm-tools 2.1.29 → 2.1.30

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.29",
3
+ "version": "2.1.30",
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
@@ -37,7 +37,7 @@
37
37
  ],
38
38
  "PostToolUse": [
39
39
  {
40
- "matcher": "Bash",
40
+ "matcher": "Bash|execute_shell_command|bash",
41
41
  "script": "beads-claim-sync.mjs",
42
42
  "timeout": 5000
43
43
  },
@@ -10,10 +10,18 @@ export default function (pi: ExtensionAPI) {
10
10
  const getCwd = (ctx: any) => ctx.cwd || process.cwd();
11
11
  const isBeadsProject = (cwd: string) => fs.existsSync(path.join(cwd, ".beads"));
12
12
 
13
- // Pin session to PID stable for the life of the Pi process, consistent across all extension handlers
14
- const sessionId = process.pid.toString();
13
+ let cachedSessionId: string | null = null;
15
14
 
16
- const getSessionClaim = async (cwd: string): Promise<string | null> => {
15
+ // Resolve a stable session ID across event types.
16
+ const getSessionId = (ctx: any): string => {
17
+ const fromManager = ctx?.sessionManager?.getSessionId?.();
18
+ const fromContext = ctx?.sessionId ?? ctx?.session_id;
19
+ const resolved = fromManager || fromContext || cachedSessionId || process.pid.toString();
20
+ if (resolved && !cachedSessionId) cachedSessionId = resolved;
21
+ return resolved;
22
+ };
23
+
24
+ const getSessionClaim = async (sessionId: string, cwd: string): Promise<string | null> => {
17
25
  const result = await SubprocessRunner.run("bd", ["kv", "get", `claimed:${sessionId}`], { cwd });
18
26
  if (result.code === 0) return result.stdout.trim();
19
27
  return null;
@@ -32,12 +40,18 @@ export default function (pi: ExtensionAPI) {
32
40
  return false;
33
41
  };
34
42
 
43
+ pi.on("session_start", async (_event, ctx) => {
44
+ cachedSessionId = ctx?.sessionManager?.getSessionId?.() ?? ctx?.sessionId ?? ctx?.session_id ?? cachedSessionId;
45
+ return undefined;
46
+ });
47
+
35
48
  pi.on("tool_call", async (event, ctx) => {
36
49
  const cwd = getCwd(ctx);
37
50
  if (!isBeadsProject(cwd)) return undefined;
51
+ const sessionId = getSessionId(ctx);
38
52
 
39
53
  if (EventAdapter.isMutatingFileTool(event)) {
40
- const claim = await getSessionClaim(cwd);
54
+ const claim = await getSessionClaim(sessionId, cwd);
41
55
  if (!claim) {
42
56
  const hasWork = await hasTrackableWork(cwd);
43
57
  if (hasWork) {
@@ -46,7 +60,7 @@ export default function (pi: ExtensionAPI) {
46
60
  }
47
61
  return {
48
62
  block: true,
49
- reason: `No active issue claim for this session (pid:${sessionId}).\n bd update <id> --claim\n bd kv set "claimed:${sessionId}" "<id>"`,
63
+ reason: `No active issue claim for this session (${sessionId}).\n bd update <id> --claim\n bd kv set "claimed:${sessionId}" "<id>"`,
50
64
  };
51
65
  }
52
66
  }
@@ -55,7 +69,7 @@ export default function (pi: ExtensionAPI) {
55
69
  if (isToolCallEventType("bash", event)) {
56
70
  const command = event.input.command;
57
71
  if (command && /\bgit\s+commit\b/.test(command)) {
58
- const claim = await getSessionClaim(cwd);
72
+ const claim = await getSessionClaim(sessionId, cwd);
59
73
  if (claim) {
60
74
  return {
61
75
  block: true,
@@ -71,6 +85,7 @@ export default function (pi: ExtensionAPI) {
71
85
  pi.on("tool_result", async (event, ctx) => {
72
86
  if (isBashToolResult(event)) {
73
87
  const command = event.input.command;
88
+ const sessionId = getSessionId(ctx);
74
89
 
75
90
  // Auto-claim on bd update --claim regardless of exit code.
76
91
  // bd returns exit 1 with "already in_progress" when status unchanged — still a valid claim intent.
@@ -80,7 +95,7 @@ export default function (pi: ExtensionAPI) {
80
95
  const issueId = issueMatch[1];
81
96
  const cwd = getCwd(ctx);
82
97
  await SubprocessRunner.run("bd", ["kv", "set", `claimed:${sessionId}`, issueId], { cwd });
83
- const claimNotice = `\n\n✅ **Beads**: Session \`pid:${sessionId}\` claimed issue \`${issueId}\`. File edits are now unblocked.`;
98
+ const claimNotice = `\n\n✅ **Beads**: Session \`${sessionId}\` claimed issue \`${issueId}\`. File edits are now unblocked.`;
84
99
  return { content: [...event.content, { type: "text", text: claimNotice }] };
85
100
  }
86
101
  }
@@ -95,8 +95,8 @@ export default function (pi: ExtensionAPI) {
95
95
 
96
96
  pi.on("session_start", async (_event, ctx) => {
97
97
  capturedCtx = ctx;
98
- // Get session ID from sessionManager (UUID, consistent with hooks)
99
- sessionId = ctx.sessionManager?.getSessionId?.() ?? process.pid.toString();
98
+ // Get session ID from sessionManager/context (prefer UUID, consistent with hooks)
99
+ sessionId = ctx.sessionManager?.getSessionId?.() ?? ctx.sessionId ?? ctx.session_id ?? process.pid.toString();
100
100
 
101
101
  ctx.ui.setFooter((tui, theme, footerData) => {
102
102
  requestRender = () => tui.requestRender();
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // beads-claim-sync — PostToolUse hook
3
3
  // Auto-sets bd kv claim when bd update --claim is detected.
4
- // Uses session_id from hook input (UUID, matches Pi's sessionManager.getSessionId())
5
4
 
6
5
  import { spawnSync } from 'node:child_process';
7
6
  import { readFileSync, existsSync } from 'node:fs';
@@ -19,10 +18,28 @@ function isBeadsProject(cwd) {
19
18
  return existsSync(join(cwd, '.beads'));
20
19
  }
21
20
 
21
+ function isShellTool(toolName) {
22
+ return toolName === 'Bash' || toolName === 'bash' || toolName === 'execute_shell_command';
23
+ }
24
+
25
+ function commandSucceeded(payload) {
26
+ const tr = payload?.tool_response ?? payload?.tool_result ?? payload?.result;
27
+ if (!tr || typeof tr !== 'object') return true;
28
+
29
+ if (tr.success === false) return false;
30
+ if (tr.error) return false;
31
+
32
+ const numeric = [tr.exit_code, tr.exitCode, tr.status, tr.returncode].find((v) => Number.isInteger(v));
33
+ if (typeof numeric === 'number' && numeric !== 0) return false;
34
+
35
+ return true;
36
+ }
37
+
22
38
  function main() {
23
39
  const input = readInput();
24
40
  if (!input || input.hook_event_name !== 'PostToolUse') process.exit(0);
25
- if (input.tool_name !== 'Bash') process.exit(0);
41
+ if (!isShellTool(input.tool_name)) process.exit(0);
42
+ if (!commandSucceeded(input)) process.exit(0);
26
43
 
27
44
  const cwd = input.cwd || process.cwd();
28
45
  if (!isBeadsProject(cwd)) process.exit(0);
@@ -32,33 +49,33 @@ function main() {
32
49
  process.exit(0);
33
50
  }
34
51
 
35
- // Extract issue ID from command
36
52
  const match = command.match(/\bbd\s+update\s+(\S+)/);
37
53
  if (!match) process.exit(0);
38
54
 
39
55
  const issueId = match[1];
40
- // Use session_id from hook input (UUID from Pi/Claude Code)
41
- const sessionId = input.session_id;
56
+ const sessionId = input.session_id ?? input.sessionId;
42
57
 
43
58
  if (!sessionId) {
44
59
  process.stderr.write('Beads claim sync: no session_id in hook input\n');
45
60
  process.exit(0);
46
61
  }
47
62
 
48
- try {
49
- spawnSync('bd', ['kv', 'set', `claimed:${sessionId}`, issueId], {
50
- cwd,
51
- stdio: ['pipe', 'pipe', 'pipe'],
52
- timeout: 5000,
53
- });
54
- process.stdout.write(JSON.stringify({
55
- additionalContext: `\n✅ **Beads**: Session \`${sessionId}\` claimed issue \`${issueId}\`.`,
56
- }));
57
- process.stdout.write('\n');
58
- } catch (err) {
59
- process.stderr.write(`Beads claim sync warning: ${err.message}\n`);
63
+ const result = spawnSync('bd', ['kv', 'set', `claimed:${sessionId}`, issueId], {
64
+ cwd,
65
+ stdio: ['pipe', 'pipe', 'pipe'],
66
+ timeout: 5000,
67
+ });
68
+
69
+ if (result.status !== 0) {
70
+ const err = (result.stderr || result.stdout || '').toString().trim();
71
+ if (err) process.stderr.write(`Beads claim sync warning: ${err}\n`);
72
+ process.exit(0);
60
73
  }
61
74
 
75
+ process.stdout.write(JSON.stringify({
76
+ additionalContext: `\n✅ **Beads**: Session \`${sessionId}\` claimed issue \`${issueId}\`.`,
77
+ }));
78
+ process.stdout.write('\n');
62
79
  process.exit(0);
63
80
  }
64
81
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xtrm-tools",
3
- "version": "2.1.29",
3
+ "version": "2.1.30",
4
4
  "description": "Claude Code tools installer (skills, hooks, MCP servers)",
5
5
  "license": "MIT",
6
6
  "type": "module",