xtrm-tools 0.7.7 → 0.7.9

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.
Files changed (123) hide show
  1. package/.xtrm/config/hooks.json +0 -3
  2. package/.xtrm/registry.json +537 -565
  3. package/.xtrm/skills/default/gitnexus-cli/SKILL.md +82 -0
  4. package/.xtrm/skills/default/gitnexus-exploring/SKILL.md +78 -0
  5. package/.xtrm/skills/default/gitnexus-guide/SKILL.md +64 -0
  6. package/.xtrm/skills/default/init-session/SKILL.md +3 -0
  7. package/.xtrm/skills/default/last30days/SKILL.md +1 -1
  8. package/.xtrm/skills/default/last30days/scripts/lib/youtube_yt.py +59 -40
  9. package/.xtrm/skills/default/sync-docs/references/doc-structure.md +1 -1
  10. package/.xtrm/skills/default/sync-docs/references/schema.md +1 -1
  11. package/.xtrm/skills/default/sync-docs/scripts/doc_structure_analyzer.py +2 -2
  12. package/.xtrm/skills/default/using-specialists/SKILL.md +346 -138
  13. package/.xtrm/skills/default/using-specialists/SKILL.safe.md +1082 -0
  14. package/.xtrm/skills/default/using-specialists/SKILL.ultra.md +1082 -0
  15. package/.xtrm/skills/default/vaultctl/SKILL.md +150 -0
  16. package/CHANGELOG.md +4 -4
  17. package/cli/dist/index.cjs +328 -192
  18. package/cli/dist/index.cjs.map +1 -1
  19. package/cli/package.json +1 -1
  20. package/package.json +8 -8
  21. package/packages/pi-extensions/MIGRATION_NOTES.md +39 -0
  22. package/packages/pi-extensions/README.md +43 -0
  23. package/packages/pi-extensions/extensions/README.md +5 -0
  24. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/beads/index.ts +1 -1
  25. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/beads/package.json +1 -4
  26. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/custom-footer/index.ts +1 -1
  27. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/custom-footer/package.json +1 -4
  28. package/packages/pi-extensions/extensions/custom-provider-qwen-cli/package.json +16 -0
  29. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/pi-serena-compact/package.json +9 -2
  30. package/{.xtrm/ext-src → packages/pi-extensions/extensions}/quality-gates/index.ts +1 -1
  31. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/quality-gates/package.json +1 -4
  32. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/service-skills/index.ts +1 -1
  33. package/{.xtrm/ext-src → packages/pi-extensions/extensions}/service-skills/package.json +1 -4
  34. package/{.xtrm/ext-src → packages/pi-extensions/extensions}/session-flow/index.ts +1 -1
  35. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/session-flow/package.json +1 -4
  36. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-loader/index.ts +1 -1
  37. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-loader/package.json +1 -4
  38. package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-ui/index.ts +1 -1
  39. package/packages/pi-extensions/package.json +46 -0
  40. package/packages/pi-extensions/src/core/README.md +9 -0
  41. package/{.xtrm/ext-src/core/lib.ts → packages/pi-extensions/src/core/index.ts} +3 -1
  42. package/packages/pi-extensions/src/extensions/auto-session-name.ts +3 -0
  43. package/packages/pi-extensions/src/extensions/auto-update.ts +3 -0
  44. package/packages/pi-extensions/src/extensions/beads.ts +3 -0
  45. package/packages/pi-extensions/src/extensions/compact-header.ts +3 -0
  46. package/packages/pi-extensions/src/extensions/custom-footer.ts +3 -0
  47. package/packages/pi-extensions/src/extensions/custom-provider-qwen-cli.ts +3 -0
  48. package/packages/pi-extensions/src/extensions/git-checkpoint.ts +3 -0
  49. package/packages/pi-extensions/src/extensions/lsp-bootstrap.ts +3 -0
  50. package/packages/pi-extensions/src/extensions/pi-serena-compact.ts +3 -0
  51. package/packages/pi-extensions/src/extensions/quality-gates.ts +3 -0
  52. package/packages/pi-extensions/src/extensions/service-skills.ts +3 -0
  53. package/packages/pi-extensions/src/extensions/session-flow.ts +3 -0
  54. package/packages/pi-extensions/src/extensions/xtrm-loader.ts +3 -0
  55. package/packages/pi-extensions/src/extensions/xtrm-ui.ts +3 -0
  56. package/packages/pi-extensions/src/index.ts +9 -0
  57. package/packages/pi-extensions/src/registry.ts +52 -0
  58. package/packages/pi-extensions/src/shared/index.ts +1 -0
  59. package/packages/pi-extensions/src/shared/legacy-path-map.ts +23 -0
  60. package/.xtrm/config/pi/extensions/core/package.json +0 -18
  61. package/.xtrm/config/pi/extensions/custom-provider-qwen-cli/package.json +0 -1
  62. package/.xtrm/config/pi/extensions/quality-gates/index.ts +0 -66
  63. package/.xtrm/config/pi/extensions/service-skills/package.json +0 -19
  64. package/.xtrm/config/pi/extensions/session-flow/index.ts +0 -96
  65. package/.xtrm/config/pi/extensions/xtrm-ui/themes/pidex-dark.json +0 -89
  66. package/.xtrm/config/pi/extensions/xtrm-ui/themes/pidex-light.json +0 -89
  67. package/.xtrm/ext-src/auto-session-name/index.ts +0 -29
  68. package/.xtrm/ext-src/auto-session-name/package.json +0 -16
  69. package/.xtrm/ext-src/auto-update/index.ts +0 -71
  70. package/.xtrm/ext-src/auto-update/package.json +0 -16
  71. package/.xtrm/ext-src/beads/index.ts +0 -232
  72. package/.xtrm/ext-src/beads/package.json +0 -19
  73. package/.xtrm/ext-src/compact-header/index.ts +0 -69
  74. package/.xtrm/ext-src/compact-header/package.json +0 -16
  75. package/.xtrm/ext-src/core/adapter.ts +0 -52
  76. package/.xtrm/ext-src/core/guard-rules.ts +0 -100
  77. package/.xtrm/ext-src/core/logger.ts +0 -45
  78. package/.xtrm/ext-src/core/package.json +0 -18
  79. package/.xtrm/ext-src/core/runner.ts +0 -71
  80. package/.xtrm/ext-src/core/session-state.ts +0 -59
  81. package/.xtrm/ext-src/custom-footer/index.ts +0 -398
  82. package/.xtrm/ext-src/custom-footer/package.json +0 -19
  83. package/.xtrm/ext-src/custom-provider-qwen-cli/index.ts +0 -363
  84. package/.xtrm/ext-src/custom-provider-qwen-cli/package.json +0 -1
  85. package/.xtrm/ext-src/git-checkpoint/index.ts +0 -53
  86. package/.xtrm/ext-src/git-checkpoint/package.json +0 -16
  87. package/.xtrm/ext-src/lsp-bootstrap/index.ts +0 -134
  88. package/.xtrm/ext-src/lsp-bootstrap/package.json +0 -17
  89. package/.xtrm/ext-src/pi-serena-compact/index.ts +0 -121
  90. package/.xtrm/ext-src/pi-serena-compact/package.json +0 -16
  91. package/.xtrm/ext-src/quality-gates/package.json +0 -19
  92. package/.xtrm/ext-src/service-skills/index.ts +0 -108
  93. package/.xtrm/ext-src/session-flow/package.json +0 -19
  94. package/.xtrm/ext-src/xtrm-loader/index.ts +0 -152
  95. package/.xtrm/ext-src/xtrm-loader/package.json +0 -19
  96. package/.xtrm/ext-src/xtrm-ui/format.ts +0 -282
  97. package/.xtrm/ext-src/xtrm-ui/index.ts +0 -1112
  98. package/.xtrm/ext-src/xtrm-ui/package.json +0 -21
  99. /package/.xtrm/{ext-src → config/pi/extensions}/custom-footer/.pi/structured-returns/83051fe4-97da-4e2c-bdaa-343b32f4e714.combined.log +0 -0
  100. /package/.xtrm/{ext-src → config/pi/extensions}/custom-footer/.pi/structured-returns/83051fe4-97da-4e2c-bdaa-343b32f4e714.stderr.log +0 -0
  101. /package/.xtrm/{ext-src → config/pi/extensions}/custom-footer/.pi/structured-returns/83051fe4-97da-4e2c-bdaa-343b32f4e714.stdout.log +0 -0
  102. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/auto-session-name/index.ts +0 -0
  103. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/auto-session-name/package.json +0 -0
  104. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/auto-update/index.ts +0 -0
  105. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/auto-update/package.json +0 -0
  106. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/compact-header/index.ts +0 -0
  107. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/compact-header/package.json +0 -0
  108. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/custom-provider-qwen-cli/index.ts +0 -0
  109. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/git-checkpoint/index.ts +0 -0
  110. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/git-checkpoint/package.json +0 -0
  111. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/lsp-bootstrap/index.ts +0 -0
  112. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/lsp-bootstrap/package.json +0 -0
  113. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/pi-serena-compact/index.ts +0 -0
  114. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-ui/format.ts +0 -0
  115. /package/{.xtrm/config/pi → packages/pi-extensions}/extensions/xtrm-ui/package.json +0 -0
  116. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/adapter.ts +0 -0
  117. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/guard-rules.ts +0 -0
  118. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/lib.ts +0 -0
  119. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/logger.ts +0 -0
  120. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/runner.ts +0 -0
  121. /package/{.xtrm/config/pi/extensions → packages/pi-extensions/src}/core/session-state.ts +0 -0
  122. /package/{.xtrm/ext-src/xtrm-ui/themes → packages/pi-extensions/themes/xtrm-ui}/pidex-dark.json +0 -0
  123. /package/{.xtrm/ext-src/xtrm-ui/themes → packages/pi-extensions/themes/xtrm-ui}/pidex-light.json +0 -0
@@ -1,232 +0,0 @@
1
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
- import { isToolCallEventType, isBashToolResult } from "@mariozechner/pi-coding-agent";
3
- import { SubprocessRunner, EventAdapter } from "@xtrm/pi-core";
4
-
5
- export default function (pi: ExtensionAPI) {
6
- const getCwd = (ctx: any) => ctx.cwd || process.cwd();
7
-
8
- let cachedSessionId: string | null = null;
9
- let memoryGateFired = false;
10
-
11
- // Resolve a stable session ID across event types.
12
- const getSessionId = (ctx: any): string => {
13
- const fromManager = ctx?.sessionManager?.getSessionId?.();
14
- const fromContext = ctx?.sessionId ?? ctx?.session_id;
15
- const resolved = fromManager || fromContext || cachedSessionId || process.pid.toString();
16
- if (resolved && !cachedSessionId) cachedSessionId = resolved;
17
- return resolved;
18
- };
19
-
20
- const getSessionClaim = async (sessionId: string, cwd: string): Promise<string | null> => {
21
- const result = await SubprocessRunner.run("bd", ["kv", "get", `claimed:${sessionId}`], { cwd });
22
- if (result.code !== 0) return null;
23
- const claim = result.stdout.trim();
24
- return claim.length > 0 ? claim : null;
25
- };
26
-
27
- const clearClaimMarker = async (sessionId: string, cwd: string) => {
28
- await SubprocessRunner.run("bd", ["kv", "clear", `claimed:${sessionId}`], { cwd });
29
- };
30
-
31
- const isIssueInProgress = async (issueId: string, cwd: string): Promise<boolean | null> => {
32
- const result = await SubprocessRunner.run("bd", ["show", issueId, "--json"], { cwd });
33
- if (result.code !== 0 || !result.stdout.trim()) return null;
34
- try {
35
- const parsed = JSON.parse(result.stdout);
36
- const issue = Array.isArray(parsed) ? parsed[0] : parsed;
37
- if (!issue?.status) return null;
38
- return issue.status === "in_progress";
39
- } catch {
40
- return null;
41
- }
42
- };
43
-
44
- const getActiveClaim = async (sessionId: string, cwd: string): Promise<string | null> => {
45
- const claim = await getSessionClaim(sessionId, cwd);
46
- if (!claim) return null;
47
-
48
- const inProgress = await isIssueInProgress(claim, cwd);
49
- if (inProgress === false) {
50
- await clearClaimMarker(sessionId, cwd);
51
- return null;
52
- }
53
-
54
- return claim;
55
- };
56
-
57
- const getClosedThisSession = async (sessionId: string, cwd: string): Promise<string | null> => {
58
- const result = await SubprocessRunner.run("bd", ["kv", "get", `closed-this-session:${sessionId}`], { cwd });
59
- if (result.code !== 0) return null;
60
- const issue = result.stdout.trim();
61
- return issue.length > 0 ? issue : null;
62
- };
63
-
64
- const clearSessionMarkers = async (sessionId: string, cwd: string) => {
65
- await SubprocessRunner.run("bd", ["kv", "clear", `claimed:${sessionId}`], { cwd });
66
- await SubprocessRunner.run("bd", ["kv", "clear", `closed-this-session:${sessionId}`], { cwd });
67
- };
68
-
69
- const hasTrackableWork = async (cwd: string): Promise<boolean> => {
70
- const result = await SubprocessRunner.run("bd", ["list"], { cwd });
71
- if (result.code === 0) {
72
- const counts = EventAdapter.parseBdCounts(result.stdout);
73
- if (counts) return (counts.open + counts.inProgress) > 0;
74
- }
75
- return false;
76
- };
77
-
78
- const stripQuoted = (command: string): string => command.replace(/'[^']*'|"[^"]*"/g, "");
79
- const isSpecialistsSubprocessCommand = (commandUnquoted: string): boolean =>
80
- /\bspecialists\s+(run|resume|result|feed|stop|status)\b/.test(commandUnquoted);
81
-
82
- const getClosedIssueIdFromCommand = (commandUnquoted: string): string | null => {
83
- const match = commandUnquoted.match(/\bbd\s+close\s+(\S+)/);
84
- const issueId = match?.[1]?.trim();
85
- if (!issueId || issueId.startsWith("-")) return null;
86
- return issueId;
87
- };
88
-
89
- const hasIssueMemoryAck = async (issueId: string, cwd: string): Promise<boolean> => {
90
- const result = await SubprocessRunner.run("bd", ["kv", "get", `memory-acked:${issueId}`], { cwd });
91
- return result.code === 0 && result.stdout.trim().length > 0;
92
- };
93
-
94
- const closeMemoryBlockReason = (issueId: string): string =>
95
- `MEMORY_GATE_BLOCK issue=${issueId} run="bd remember '<insight>' && bd kv set 'memory-acked:${issueId}' 'saved:<key>'" or="bd kv set 'memory-acked:${issueId}' 'nothing novel:<reason>'" then="bd close ${issueId} --reason='<reason>'"`;
96
-
97
- pi.on("session_start", async (_event, ctx) => {
98
- cachedSessionId = ctx?.sessionManager?.getSessionId?.() ?? ctx?.sessionId ?? ctx?.session_id ?? cachedSessionId;
99
- return undefined;
100
- });
101
-
102
- pi.on("tool_call", async (event, ctx) => {
103
- const cwd = getCwd(ctx);
104
- if (!EventAdapter.isBeadsProject(cwd)) return undefined;
105
- const sessionId = getSessionId(ctx);
106
-
107
- if (EventAdapter.isMutatingFileTool(event)) {
108
- const claim = await getActiveClaim(sessionId, cwd);
109
- if (!claim) {
110
- const hasWork = await hasTrackableWork(cwd);
111
- if (hasWork) {
112
- if (ctx.hasUI) {
113
- ctx.ui.notify("Beads: Edit blocked. Claim an issue first.", "warning");
114
- }
115
- return {
116
- block: true,
117
- reason: `No active claim for session ${sessionId}.\n bd update <id> --claim\n`,
118
- };
119
- }
120
- }
121
- }
122
-
123
- if (isToolCallEventType("bash", event)) {
124
- const command = event.input.command ?? "";
125
- const commandUnquoted = stripQuoted(command);
126
-
127
- if (isSpecialistsSubprocessCommand(commandUnquoted)) return undefined;
128
-
129
- const closedIssueId = getClosedIssueIdFromCommand(commandUnquoted);
130
- if (closedIssueId) {
131
- const acked = await hasIssueMemoryAck(closedIssueId, cwd);
132
- if (!acked) {
133
- return {
134
- block: true,
135
- reason: closeMemoryBlockReason(closedIssueId),
136
- };
137
- }
138
- }
139
-
140
- if (/\bgit\s+commit\b/.test(commandUnquoted)) {
141
- const claim = await getActiveClaim(sessionId, cwd);
142
- if (claim) {
143
- return {
144
- block: true,
145
- reason: `Active claim [${claim}] — close it first.\n bd close ${claim}\n (Pi workflow) publish/merge are external steps; do not rely on xtrm finish.\n`,
146
- };
147
- }
148
- }
149
- }
150
-
151
- return undefined;
152
- });
153
-
154
- pi.on("tool_result", async (event, ctx) => {
155
- if (!isBashToolResult(event)) return undefined;
156
-
157
- const command = event.input.command || "";
158
- const sessionId = getSessionId(ctx);
159
- const cwd = getCwd(ctx);
160
-
161
- // Auto-claim on bd update --claim regardless of exit code.
162
- if (/\bbd\s+update\b/.test(command) && /--claim\b/.test(command)) {
163
- const issueMatch = command.match(/\bbd\s+update\s+(\S+)/);
164
- if (issueMatch) {
165
- const issueId = issueMatch[1];
166
- await SubprocessRunner.run("bd", ["kv", "set", `claimed:${sessionId}`, issueId], { cwd });
167
- memoryGateFired = false;
168
- const claimNotice = `\n\n✅ **Beads**: Session \`${sessionId}\` claimed issue \`${issueId}\`. File edits are now unblocked.`;
169
- return { content: [...event.content, { type: "text", text: claimNotice }] };
170
- }
171
- }
172
-
173
- if (/\bbd\s+close\b/.test(command) && !event.isError) {
174
- const closeMatch = command.match(/\bbd\s+close\s+(\S+)/);
175
- const closedIssueId = closeMatch?.[1] ?? null;
176
-
177
- if (closedIssueId) {
178
- await SubprocessRunner.run("bd", ["kv", "set", `closed-this-session:${sessionId}`, closedIssueId], { cwd });
179
- memoryGateFired = false;
180
- }
181
-
182
- const memoryGateText = closedIssueId
183
- ? `\n\n**Beads Memory Gate**: close-time memory ack verified for \`${closedIssueId}\` (\`memory-acked:${closedIssueId}\`).`
184
- : `\n\n**Beads**: Work completed. Consider if this session produced insights worth persisting via \`bd remember\`.`;
185
- return { content: [...event.content, { type: "text", text: memoryGateText }] };
186
- }
187
-
188
- return undefined;
189
- });
190
-
191
- // Memory gate: clean up session markers and check ack at agent_end/session_shutdown.
192
- // Memory gate prompt was already injected into bd close tool_result context (silent, agent-visible only).
193
- // No UI notification — parity with Claude Stop hook {additionalContext} pattern.
194
- const triggerMemoryGateIfNeeded = async (ctx: any) => {
195
- const cwd = getCwd(ctx);
196
- if (!EventAdapter.isBeadsProject(cwd)) return;
197
- const sessionId = getSessionId(ctx);
198
-
199
- const markerCheck = await SubprocessRunner.run("bd", ["kv", "get", `memory-gate-done:${sessionId}`], { cwd });
200
- if (markerCheck.code === 0) {
201
- await SubprocessRunner.run("bd", ["kv", "clear", `memory-gate-done:${sessionId}`], { cwd });
202
- await clearSessionMarkers(sessionId, cwd);
203
- memoryGateFired = false;
204
- return;
205
- }
206
-
207
- if (memoryGateFired) return;
208
-
209
- const closedIssueId = await getClosedThisSession(sessionId, cwd);
210
- if (!closedIssueId) return;
211
-
212
- const closeTimeAcked = await hasIssueMemoryAck(closedIssueId, cwd);
213
- if (closeTimeAcked) {
214
- await SubprocessRunner.run("bd", ["kv", "clear", `closed-this-session:${sessionId}`], { cwd });
215
- memoryGateFired = false;
216
- return;
217
- }
218
-
219
- memoryGateFired = true;
220
- // No notify — memory gate was injected into bd close tool_result content (silent, agent-visible only).
221
- };
222
-
223
- pi.on("agent_end", async (_event, ctx) => {
224
- await triggerMemoryGateIfNeeded(ctx);
225
- return undefined;
226
- });
227
-
228
- pi.on("session_shutdown", async (_event, ctx) => {
229
- await triggerMemoryGateIfNeeded(ctx);
230
- return undefined;
231
- });
232
- }
@@ -1,19 +0,0 @@
1
- {
2
- "name": "@xtrm/pi-beads",
3
- "version": "1.0.0",
4
- "description": "xtrm Pi extension: beads",
5
- "type": "module",
6
- "exports": {
7
- ".": "./index.ts"
8
- },
9
- "keywords": [
10
- "pi",
11
- "extension",
12
- "xtrm"
13
- ],
14
- "author": "xtrm",
15
- "license": "MIT",
16
- "dependencies": {
17
- "@xtrm/pi-core": "^1.0.0"
18
- }
19
- }
@@ -1,69 +0,0 @@
1
- /**
2
- * oh-pi Compact Header — table-style startup info with dynamic column widths
3
- */
4
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
5
- import { VERSION } from "@mariozechner/pi-coding-agent";
6
- import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
7
-
8
- export default function (pi: ExtensionAPI) {
9
- pi.on("session_start", async (_event, ctx) => {
10
- if (!ctx.hasUI) return;
11
-
12
- ctx.ui.setHeader((_tui, theme) => ({
13
- render(width: number): string[] {
14
- const d = (s: string) => theme.fg("dim", s);
15
- const a = (s: string) => theme.fg("accent", s);
16
-
17
- const cmds = pi.getCommands();
18
- const prompts = cmds.filter(c => c.source === "prompt").map(c => `/${c.name}`).join(" ");
19
- const skills = cmds.filter(c => c.source === "skill").map(c => c.name).join(" ");
20
- const model = ctx.model ? `${ctx.model.id}` : "no model";
21
- const thinking = pi.getThinkingLevel();
22
- const provider = ctx.model?.provider ?? "";
23
-
24
- const pad = (s: string, w: number) => s + " ".repeat(Math.max(0, w - visibleWidth(s)));
25
- const t = (s: string) => truncateToWidth(s, width);
26
- const sep = d(" │ ");
27
-
28
- // Right two columns are fixed width
29
- const rCol = [
30
- [d("esc"), a("interrupt"), d("S-tab"), a("thinking")],
31
- [d("^C"), a("clear/exit"), d("^O"), a("expand")],
32
- [d("^P"), a("model"), d("^G"), a("editor")],
33
- [d("/"), a("commands"), d("^V"), a("paste")],
34
- [d("!"), a("bash"), d(""), a("")],
35
- ];
36
- const k1w = 6, v1w = 13, k2w = 6, v2w = 9;
37
- const rightW = k1w + v1w + 3 + k2w + v2w + 3; // 3 for each sep
38
-
39
- // Left column gets remaining space
40
- const leftW = Math.max(20, width - rightW);
41
- const lk = 9; // label width
42
-
43
- const lCol = [
44
- [d("version"), a(`v${VERSION} ${provider}`)],
45
- [d("model"), a(model)],
46
- [d("think"), a(thinking)],
47
- [d(""), d("")],
48
- [d(""), d("")],
49
- ];
50
-
51
- const lines: string[] = [""];
52
- for (let i = 0; i < 5; i++) {
53
- const [lk0, lv0] = lCol[i];
54
- const [rk0, rv0, rk1, rv1] = rCol[i];
55
- const left = truncateToWidth(pad(lk0, lk) + lv0, leftW);
56
- const right = pad(rk0, k1w) + pad(rv0, v1w) + sep + pad(rk1, k2w) + rv1;
57
- lines.push(t(pad(left, leftW) + sep + right));
58
- }
59
-
60
- if (prompts) lines.push(t(`${pad(d("prompts"), lk)}${a(prompts)}`));
61
- if (skills) lines.push(t(`${pad(d("skills"), lk)}${a(skills)}`));
62
- lines.push(d("─".repeat(width)));
63
-
64
- return lines;
65
- },
66
- invalidate() {},
67
- }));
68
- });
69
- }
@@ -1,16 +0,0 @@
1
- {
2
- "name": "@xtrm/pi-compact-header",
3
- "version": "1.0.0",
4
- "description": "xtrm Pi extension: compact-header",
5
- "type": "module",
6
- "exports": {
7
- ".": "./index.ts"
8
- },
9
- "keywords": [
10
- "pi",
11
- "extension",
12
- "xtrm"
13
- ],
14
- "author": "xtrm",
15
- "license": "MIT"
16
- }
@@ -1,52 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as nodePath from "node:path";
3
-
4
- import type { ExtensionAPI, ToolCallEvent } from "@mariozechner/pi-coding-agent";
5
- import { PI_MUTATING_FILE_TOOLS } from "./guard-rules";
6
-
7
- export class EventAdapter {
8
- /**
9
- * Checks if the tool event is a mutating file operation (write, edit, etc).
10
- */
11
- static isMutatingFileTool(event: ToolCallEvent<any, any>): boolean {
12
- return PI_MUTATING_FILE_TOOLS.includes(event.toolName);
13
- }
14
-
15
- /**
16
- * Extracts the target path from a tool input, resolving against the current working directory.
17
- */
18
- static extractPathFromToolInput(event: ToolCallEvent<any, any>, cwd: string): string | null {
19
- const input = event.input;
20
- if (!input) return null;
21
-
22
- const pathRaw = input.path || input.file || input.filePath;
23
- if (typeof pathRaw === "string") {
24
- return pathRaw; // Usually Pi passes absolute paths anyway or paths relative to root
25
- }
26
-
27
- return null;
28
- }
29
-
30
- /**
31
- * Safely formats a block reason string to ensure UI readiness.
32
- */
33
- static formatBlockReason(prefix: string, details: string): string {
34
- return `${prefix}: ${details}`;
35
- }
36
- /**
37
- * Returns true if the given directory is a beads project (has a .beads directory).
38
- */
39
- static isBeadsProject(cwd: string): boolean {
40
- return fs.existsSync(nodePath.join(cwd, ".beads"));
41
- }
42
-
43
- /**
44
- * Parses the summary line from `bd list` output.
45
- * Returns { open, inProgress } or null if the line is absent.
46
- */
47
- static parseBdCounts(output: string): { open: number; inProgress: number } | null {
48
- const m = output.match(/Total:\s*\d+\s+issues?\s*\((\d+)\s+open,\s*(\d+)\s+in progress\)/);
49
- if (!m) return null;
50
- return { open: parseInt(m[1], 10), inProgress: parseInt(m[2], 10) };
51
- }
52
- }
@@ -1,100 +0,0 @@
1
- // Canonical guard-rule constants for Pi extensions.
2
- // Mirrors hooks/guard-rules.mjs with Pi-specific tool naming where needed.
3
-
4
- export const PI_MUTATING_FILE_TOOLS = [
5
- "write",
6
- "edit",
7
- "replace_content",
8
- "replace_lines",
9
- "delete_lines",
10
- "insert_at_line",
11
- "create_text_file",
12
- "rename_symbol",
13
- "replace_symbol_body",
14
- "insert_after_symbol",
15
- "insert_before_symbol",
16
- ];
17
-
18
- export const SAFE_BASH_PREFIXES = [
19
- // Git read-only
20
- "git status",
21
- "git log",
22
- "git diff",
23
- "git show",
24
- "git blame",
25
- "git branch",
26
- "git fetch",
27
- "git remote",
28
- "git config",
29
- "git pull",
30
- "git stash",
31
- "git worktree",
32
- "git checkout -b",
33
- "git switch -c",
34
- // Tools
35
- "gh",
36
- "bd",
37
- "npx gitnexus",
38
- "xtrm finish",
39
- // Read-only filesystem
40
- "cat",
41
- "ls",
42
- "head",
43
- "tail",
44
- "pwd",
45
- "which",
46
- "type",
47
- "env",
48
- "printenv",
49
- "find",
50
- "grep",
51
- "rg",
52
- "fd",
53
- "wc",
54
- "sort",
55
- "uniq",
56
- "cut",
57
- "awk",
58
- "jq",
59
- "yq",
60
- "bat",
61
- "less",
62
- "more",
63
- "file",
64
- "stat",
65
- "du",
66
- "tree",
67
- ];
68
-
69
- export const DANGEROUS_BASH_PATTERNS = [
70
- "sed\\s+-i",
71
- "echo\\s+[^\\n]*>",
72
- "printf\\s+[^\\n]*>",
73
- "cat\\s+[^\\n]*>",
74
- "tee\\b",
75
- "(?:^|\\s)(?:vim|nano|vi)\\b",
76
- "(?:^|\\s)mv\\b",
77
- "(?:^|\\s)cp\\b",
78
- "(?:^|\\s)rm\\b",
79
- "(?:^|\\s)mkdir\\b",
80
- "(?:^|\\s)touch\\b",
81
- "(?:^|\\s)chmod\\b",
82
- "(?:^|\\s)chown\\b",
83
- ">>",
84
- "(?:^|\\s)git\\s+add\\b",
85
- "(?:^|\\s)git\\s+commit\\b",
86
- "(?:^|\\s)git\\s+merge\\b",
87
- "(?:^|\\s)git\\s+push\\b",
88
- "(?:^|\\s)git\\s+reset\\b",
89
- "(?:^|\\s)git\\s+checkout\\b",
90
- "(?:^|\\s)git\\s+rebase\\b",
91
- "(?:^|\\s)git\\s+stash\\b",
92
- "(?:^|\\s)npm\\s+install\\b",
93
- "(?:^|\\s)bun\\s+install\\b",
94
- "(?:^|\\s)bun\\s+add\\b",
95
- "(?:^|\\s)node\\s+(?:-e|--eval)\\b",
96
- "(?:^|\\s)bun\\s+(?:-e|--eval)\\b",
97
- "(?:^|\\s)python\\s+-c\\b",
98
- "(?:^|\\s)perl\\s+-e\\b",
99
- "(?:^|\\s)ruby\\s+-e\\b",
100
- ];
@@ -1,45 +0,0 @@
1
- export type LogLevel = "debug" | "info" | "warn" | "error";
2
-
3
- export interface LoggerOptions {
4
- namespace: string;
5
- level?: LogLevel;
6
- }
7
-
8
- export class Logger {
9
- private namespace: string;
10
- private level: LogLevel;
11
-
12
- constructor(options: LoggerOptions) {
13
- this.namespace = options.namespace;
14
- this.level = options.level || "info";
15
- }
16
-
17
- private shouldLog(level: LogLevel): boolean {
18
- const levels: LogLevel[] = ["debug", "info", "warn", "error"];
19
- return levels.indexOf(level) >= levels.indexOf(this.level);
20
- }
21
-
22
- debug(message: string, ...args: any[]) {
23
- if (this.shouldLog("debug")) {
24
- console.debug(`[${this.namespace}] DEBUG: ${message}`, ...args);
25
- }
26
- }
27
-
28
- info(message: string, ...args: any[]) {
29
- if (this.shouldLog("info")) {
30
- console.info(`[${this.namespace}] INFO: ${message}`, ...args);
31
- }
32
- }
33
-
34
- warn(message: string, ...args: any[]) {
35
- if (this.shouldLog("warn")) {
36
- console.warn(`[${this.namespace}] WARN: ${message}`, ...args);
37
- }
38
- }
39
-
40
- error(message: string, ...args: any[]) {
41
- if (this.shouldLog("error")) {
42
- console.error(`[${this.namespace}] ERROR: ${message}`, ...args);
43
- }
44
- }
45
- }
@@ -1,18 +0,0 @@
1
- {
2
- "name": "@xtrm/pi-core",
3
- "version": "1.0.0",
4
- "description": "Shared utilities for xtrm Pi extensions",
5
- "type": "module",
6
- "exports": {
7
- ".": "./lib.ts",
8
- "./lib": "./lib.ts",
9
- "./logger": "./logger.ts",
10
- "./runner": "./runner.ts",
11
- "./adapter": "./adapter.ts",
12
- "./guard-rules": "./guard-rules.ts",
13
- "./session-state": "./session-state.ts"
14
- },
15
- "keywords": ["pi", "extension", "xtrm"],
16
- "author": "xtrm",
17
- "license": "MIT"
18
- }
@@ -1,71 +0,0 @@
1
- import { execFile, spawnSync } from "node:child_process";
2
- import { promisify } from "node:util";
3
-
4
- const execFileAsync = promisify(execFile);
5
-
6
- export interface RunOptions {
7
- timeoutMs?: number;
8
- cwd?: string;
9
- env?: Record<string, string>;
10
- input?: string; // Standard input
11
- }
12
-
13
- export interface RunResult {
14
- code: number;
15
- stdout: string;
16
- stderr: string;
17
- }
18
-
19
- export class SubprocessRunner {
20
- /**
21
- * Run a command deterministically with a timeout and optional stdin.
22
- */
23
- static async run(
24
- command: string,
25
- args: string[],
26
- options: RunOptions = {}
27
- ): Promise<RunResult> {
28
- const timeout = options.timeoutMs ?? 10000;
29
- const cwd = options.cwd ?? process.cwd();
30
- const env = { ...process.env, ...options.env };
31
-
32
- if (options.input !== undefined) {
33
- // Use spawnSync for stdin support if input is provided
34
- const result = spawnSync(command, args, {
35
- cwd,
36
- env,
37
- input: options.input,
38
- encoding: "utf8",
39
- timeout,
40
- maxBuffer: 1024 * 1024 * 10,
41
- });
42
-
43
- return {
44
- code: result.status ?? 1,
45
- stdout: (result.stdout ?? "").trim(),
46
- stderr: (result.stderr ?? "").trim(),
47
- };
48
- }
49
-
50
- try {
51
- const result = await execFileAsync(command, args, {
52
- timeout,
53
- cwd,
54
- env,
55
- maxBuffer: 1024 * 1024 * 10,
56
- });
57
-
58
- return {
59
- code: 0,
60
- stdout: result.stdout.trim(),
61
- stderr: result.stderr.trim(),
62
- };
63
- } catch (error: any) {
64
- return {
65
- code: error.code ?? 1,
66
- stdout: (error.stdout ?? "").trim(),
67
- stderr: (error.stderr ?? error.message ?? "").trim(),
68
- };
69
- }
70
- }
71
- }
@@ -1,59 +0,0 @@
1
- import path from 'node:path';
2
- import fs from 'node:fs';
3
-
4
- export type SessionPhase =
5
- | 'claimed'
6
- | 'phase1-done'
7
- | 'waiting-merge'
8
- | 'conflicting'
9
- | 'pending-cleanup'
10
- | 'merged'
11
- | 'cleanup-done';
12
-
13
- export interface SessionState {
14
- issueId: string;
15
- branch: string;
16
- worktreePath: string;
17
- prNumber: number | null;
18
- prUrl: string | null;
19
- phase: SessionPhase;
20
- conflictFiles: string[];
21
- startedAt: string;
22
- lastChecked: string;
23
- }
24
-
25
- const SESSION_STATE_FILE = '.xtrm-session-state.json';
26
-
27
- export function findSessionStateFile(startCwd: string): string | null {
28
- let current = path.resolve(startCwd || process.cwd());
29
- for (;;) {
30
- const candidate = path.join(current, SESSION_STATE_FILE);
31
- if (fs.existsSync(candidate)) return candidate;
32
- const parent = path.dirname(current);
33
- if (parent === current) return null;
34
- current = parent;
35
- }
36
- }
37
-
38
- export function readSessionState(startCwd: string): SessionState | null {
39
- const filePath = findSessionStateFile(startCwd);
40
- if (!filePath) return null;
41
- try {
42
- const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
43
- if (!parsed || typeof parsed !== 'object') return null;
44
- if (!parsed.issueId || !parsed.branch || !parsed.worktreePath || !parsed.phase) return null;
45
- return {
46
- issueId: String(parsed.issueId),
47
- branch: String(parsed.branch),
48
- worktreePath: String(parsed.worktreePath),
49
- prNumber: parsed.prNumber ?? null,
50
- prUrl: parsed.prUrl ?? null,
51
- phase: parsed.phase,
52
- conflictFiles: Array.isArray(parsed.conflictFiles) ? parsed.conflictFiles.map(String) : [],
53
- startedAt: String(parsed.startedAt || ''),
54
- lastChecked: String(parsed.lastChecked || ''),
55
- } as SessionState;
56
- } catch {
57
- return null;
58
- }
59
- }