@cydm/pie 1.0.4 → 1.0.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.
@@ -1,161 +1,42 @@
1
1
  import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+ import {
3
+ ManageTodoListParamsSchema,
4
+ executeManageTodoList,
5
+ restoreTodosFromMessages
6
+ } from "../../../chunks/chunk-TBJ25UWB.js";
7
+ import "../../../chunks/chunk-MWFBYJOI.js";
8
+ import "../../../chunks/chunk-RID3574D.js";
9
+ import "../../../chunks/chunk-TG2EQLX2.js";
2
10
 
3
11
  // builtin/extensions/todo/index.ts
4
- async function readTodos(cwd) {
5
- try {
6
- const fs = await import("fs");
7
- const path = await import("path");
8
- const todosPath = path.join(cwd, ".pie", "todos.md");
9
- if (fs.existsSync(todosPath)) {
10
- return fs.readFileSync(todosPath, "utf-8");
11
- }
12
- } catch {
13
- }
14
- return null;
15
- }
12
+ var TOOL_DESCRIPTION = `Manage a structured todo list to track progress and plan tasks throughout your coding session.
13
+
14
+ Use this tool for complex multi-step work, especially when:
15
+ - The task needs planning or progress tracking
16
+ - The user provided multiple requests
17
+ - You need to break a larger change into actionable steps
18
+ - You are about to start or finish a tracked step
19
+
20
+ Workflow:
21
+ 1. Write the full todo list with clear action items
22
+ 2. Mark the current item as in-progress before starting it
23
+ 3. Complete the work for that item
24
+ 4. Mark it completed immediately
25
+ 5. Continue until all items are done
26
+
27
+ Always send the full list on write. Partial updates are not supported.`;
16
28
  function todoExtension(ctx) {
17
- ctx.log("Todo extension loaded");
18
- ctx.registerCommand({
19
- path: ["tools", "todo"],
20
- description: "Smart task extraction and management",
21
- handler: async (ctx2, args) => {
22
- const fs = await import("fs");
23
- const path = await import("path");
24
- const todosPath = path.join(ctx2.cwd, ".pie", "todos.md");
25
- const pieDir = path.join(ctx2.cwd, ".pie");
26
- if (!fs.existsSync(pieDir)) {
27
- fs.mkdirSync(pieDir, { recursive: true });
28
- }
29
- const command = args?.trim();
30
- const existingTodos = await readTodos(ctx2.cwd);
31
- if (command?.match(/^done\s+\d+/i)) {
32
- const match = command.match(/done\s+(\d+)/i);
33
- const id = match ? parseInt(match[1], 10) : 0;
34
- if (!existingTodos) return "No todos.md file found.";
35
- return `\u8BF7\u5C06 .pie/todos.md \u4E2D\u7B2C ${id} \u4E2A\u672A\u5B8C\u6210\u4EFB\u52A1\u6807\u8BB0\u4E3A\u5DF2\u5B8C\u6210\uFF1A
36
-
37
- \u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1A
38
- \`\`\`
39
- ${existingTodos}
40
- \`\`\`
41
-
42
- \u8981\u6C42\uFF1A
43
- 1. \u627E\u5230\u7B2C ${id} \u4E2A\u4EE5 "- [ ]" \u5F00\u5934\u7684\u4EFB\u52A1
44
- 2. \u5C06\u5176\u6539\u4E3A "- [x]"\uFF08\u4FDD\u7559\u539F\u6709\u5185\u5BB9\u548C\u4F18\u5148\u7EA7\u6807\u8BB0\uFF09
45
- 3. \u5728\u672B\u5C3E\u6DFB\u52A0\u5B8C\u6210\u65F6\u95F4\u6CE8\u91CA\uFF1A<!-- completed: YYYY-MM-DD -->
46
- 4. \u4F7F\u7528 write \u5DE5\u5177\u66F4\u65B0\u6587\u4EF6
47
- 5. \u62A5\u544A\u5B8C\u6210\u4E86\u54EA\u4E2A\u4EFB\u52A1`;
48
- }
49
- if (command?.toLowerCase() === "clear") {
50
- if (!existingTodos) return "No todos.md file found.";
51
- return `\u8BF7\u6E05\u7406 .pie/todos.md \u4E2D\u7684\u5DF2\u5B8C\u6210\u4EFB\u52A1\uFF1A
52
-
53
- \u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1A
54
- \`\`\`
55
- ${existingTodos}
56
- \`\`\`
57
-
58
- \u8981\u6C42\uFF1A
59
- 1. \u79FB\u9664\u6240\u6709\u4EE5 "- [x]" \u5F00\u5934\u7684\u5DF2\u5B8C\u6210\u4EFB\u52A1
60
- 2. \u4FDD\u7559\u6240\u6709 "- [ ]" \u672A\u5B8C\u6210\u4EFB\u52A1
61
- 3. \u4FDD\u7559\u6587\u4EF6\u6807\u9898\u548C\u5176\u4ED6\u7ED3\u6784
62
- 4. \u4F7F\u7528 write \u5DE5\u5177\u66F4\u65B0\u6587\u4EF6
63
- 5. \u62A5\u544A\u6E05\u7406\u4E86\u591A\u5C11\u4E2A\u5DF2\u5B8C\u6210\u4EFB\u52A1`;
64
- }
65
- if (command?.toLowerCase().startsWith("add ")) {
66
- const description = command.slice(4).trim();
67
- ctx2.ui.notify("\u{1F4DD} \u6B63\u5728\u5206\u6790\u4EFB\u52A1...", "info");
68
- return `\u8BF7\u5C06\u4EE5\u4E0B\u63CF\u8FF0\u8F6C\u6362\u4E3A\u7ED3\u6784\u5316\u7684 todo \u6761\u76EE\u5E76\u6DFB\u52A0\u5230 .pie/todos.md\uFF1A
69
-
70
- \u63CF\u8FF0\uFF1A"${description}"
71
-
72
- ${existingTodos ? `
73
- \u5F53\u524D todos.md \u5185\u5BB9\uFF1A
74
- \`\`\`
75
- ${existingTodos}
76
- \`\`\`` : ""}
77
-
78
- \u4EFB\u52A1\uFF1A
79
- 1. \u5206\u6790\u63CF\u8FF0\uFF0C\u786E\u5B9A\uFF1A
80
- - \u4EFB\u52A1\u7C7B\u578B\uFF1Afeature / bug / refactor / docs / test / other
81
- - \u4F18\u5148\u7EA7\uFF1Ahigh / medium / low\uFF08\u6839\u636E\u7D27\u6025\u7A0B\u5EA6\u548C\u5F71\u54CD\u5224\u65AD\uFF09
82
- - \u6E05\u6670\u7B80\u6D01\u7684\u4EFB\u52A1\u63CF\u8FF0
83
-
84
- 2. \u751F\u6210\u683C\u5F0F\uFF1A
85
- - [ ] ![{priority}] [{type}] {description}
86
-
87
- \u793A\u4F8B\uFF1A
88
- - [ ] ![high] [bug] \u4FEE\u590D\u767B\u5F55\u9875\u9762\u7684\u5185\u5B58\u6CC4\u6F0F
89
- - [ ] ![medium] [feature] \u6DFB\u52A0\u7528\u6237\u5BFC\u51FA\u529F\u80FD
90
- - [ ] ![low] [docs] \u66F4\u65B0 API \u6587\u6863
91
-
92
- 3. \u4F7F\u7528 write \u5DE5\u5177\uFF1A
93
- - \u5982\u679C\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u521B\u5EFA\u6807\u51C6\u683C\u5F0F
94
- - \u5C06\u65B0\u4EFB\u52A1\u6309\u4F18\u5148\u7EA7\u63D2\u5165\u5230 "## Active" \u90E8\u5206\u7684\u5408\u9002\u4F4D\u7F6E\uFF08high\u5728\u524D\uFF0Clow\u5728\u540E\uFF09
95
- - \u4FDD\u6301\u73B0\u6709\u4EFB\u52A1\u4E0D\u53D8
96
-
97
- 4. \u62A5\u544A\u6DFB\u52A0\u4E86\u4EC0\u4E48\u4EFB\u52A1`;
98
- }
99
- if (command?.toLowerCase() === "priority") {
100
- if (!existingTodos) return "No todos.md file found.";
101
- ctx2.ui.notify("\u{1F504} \u6B63\u5728\u6309\u4F18\u5148\u7EA7\u6392\u5E8F...", "info");
102
- return `\u8BF7\u91CD\u65B0\u6574\u7406 .pie/todos.md \u4E2D\u7684\u4EFB\u52A1\uFF0C\u6309\u4F18\u5148\u7EA7\u6392\u5E8F\uFF1A
103
-
104
- \u5F53\u524D\u6587\u4EF6\u5185\u5BB9\uFF1A
105
- \`\`\`
106
- ${existingTodos}
107
- \`\`\`
108
-
109
- \u6392\u5E8F\u89C4\u5219\uFF1A
110
- 1. \u672A\u5B8C\u6210\u4EFB\u52A1\u6309\u4F18\u5148\u7EA7\u6392\u5E8F\uFF1Ahigh \u2192 medium \u2192 low
111
- 2. \u540C\u7EA7\u4F18\u5148\u7EA7\u4FDD\u6301\u539F\u6709\u987A\u5E8F
112
- 3. \u5DF2\u5B8C\u6210\u4EFB\u52A1\u653E\u5728\u6700\u540E
113
- 4. \u4FDD\u6301\u6587\u4EF6\u5176\u4ED6\u7ED3\u6784\u4E0D\u53D8
114
-
115
- \u683C\u5F0F\u4FDD\u6301\uFF1A
116
- - [ ] ![high] [type] \u63CF\u8FF0
117
- - [ ] ![medium] [type] \u63CF\u8FF0
118
- - [ ] ![low] [type] \u63CF\u8FF0
119
-
120
- \u4F7F\u7528 write \u5DE5\u5177\u66F4\u65B0\u6587\u4EF6\u3002`;
121
- }
122
- ctx2.ui.notify("\u{1F50D} \u6B63\u5728\u5206\u6790\u5BF9\u8BDD\u63D0\u53D6\u4EFB\u52A1...", "info");
123
- return `\u{1F50D} \u667A\u80FD\u4EFB\u52A1\u63D0\u53D6
124
-
125
- \u8BF7\u5206\u6790\u6211\u4EEC\u7684\u5BF9\u8BDD\u5386\u53F2\uFF0C\u63D0\u53D6\u6240\u6709\u9700\u8981\u540E\u7EED\u5904\u7406\u7684\u4EFB\u52A1\u3002
126
-
127
- ${existingTodos ? `\u5F53\u524D\u5DF2\u6709\u4EFB\u52A1\uFF08\u907F\u514D\u91CD\u590D\uFF09\uFF1A
128
- \`\`\`
129
- ${existingTodos}
130
- \`\`\`
131
- ` : ""}
132
-
133
- \u63D0\u53D6\u6807\u51C6\uFF1A
134
- 1. **\u660E\u786E\u7684\u5F85\u529E** - "\u6211\u4EEC\u9700\u8981..."\u3001"\u8FD8\u8981..."\u3001"TODO..."
135
- 2. **\u53D1\u73B0\u7684\u95EE\u9898** - "\u8FD9\u91CC\u6709 bug"\u3001"\u9700\u8981\u4FEE\u590D..."
136
- 3. **\u6539\u8FDB\u5EFA\u8BAE** - "\u5E94\u8BE5\u4F18\u5316..."\u3001"\u53EF\u4EE5\u91CD\u6784..."
137
- 4. **\u540E\u7EED\u5DE5\u4F5C** - "\u63A5\u4E0B\u6765..."\u3001"\u4E4B\u540E\u8981..."
138
- 5. **\u672A\u5B8C\u6210\u4E8B\u9879** - \u5BF9\u8BDD\u4E2D\u63D0\u53CA\u4F46\u672A\u5B8C\u6210\u7684\u52A8\u4F5C
139
-
140
- \u5FFD\u7565\uFF1A
141
- - \u5DF2\u7ECF\u660E\u786E\u8BF4"\u5B8C\u6210"\u7684\u4E8B\u9879
142
- - \u592A\u6A21\u7CCA\u65E0\u6CD5\u6267\u884C\u7684\u60F3\u6CD5
143
- - \u5F53\u524D\u5BF9\u8BDD\u4E2D\u5DF2\u7ECF\u89E3\u51B3\u7684\u95EE\u9898
144
-
145
- \u8F93\u51FA\u683C\u5F0F\uFF08\u5982\u679C\u63D0\u53D6\u5230\u4EFB\u52A1\uFF09\uFF1A
146
-
147
- ## \u53D1\u73B0\u7684\u4EFB\u52A1
148
-
149
- | # | \u4F18\u5148\u7EA7 | \u7C7B\u578B | \u4EFB\u52A1\u63CF\u8FF0 |
150
- |---|--------|------|----------|
151
- | 1 | \u{1F534} high | bug | \u4FEE\u590D xxx \u95EE\u9898 |
152
- | 2 | \u{1F7E1} medium | feature | \u6DFB\u52A0 xxx \u529F\u80FD |
153
-
154
- \u8BF7\u4F7F\u7528 write \u5DE5\u5177\u5C06\u63D0\u53D6\u5230\u7684\u4EFB\u52A1\u6DFB\u52A0\u5230 .pie/todos.md\u3002
155
- \u5982\u679C\u6CA1\u6709\u53D1\u73B0\u65B0\u4EFB\u52A1\uFF0C\u76F4\u63A5\u62A5\u544A\u5373\u53EF\u3002`;
29
+ ctx.registerTool({
30
+ name: "manage_todo_list",
31
+ label: "manage_todo_list",
32
+ description: TOOL_DESCRIPTION,
33
+ parameters: ManageTodoListParamsSchema,
34
+ async execute(args) {
35
+ const currentTodos = restoreTodosFromMessages(ctx.getMessages());
36
+ return executeManageTodoList(args, currentTodos).result;
156
37
  }
157
38
  });
158
- ctx.log("Todo extension ready");
39
+ ctx.log("Builtin todo extension ready");
159
40
  }
160
41
  export {
161
42
  todoExtension as default
@@ -0,0 +1,9 @@
1
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+ import {
3
+ createCliHostCapabilities
4
+ } from "./chunk-JYBXCEJJ.js";
5
+ import "./chunk-RID3574D.js";
6
+ import "./chunk-TG2EQLX2.js";
7
+ export {
8
+ createCliHostCapabilities
9
+ };
@@ -0,0 +1,315 @@
1
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+ import {
3
+ Type
4
+ } from "./chunk-RID3574D.js";
5
+
6
+ // src/capabilities/bash/index.ts
7
+ import { spawn } from "child_process";
8
+ import { createWriteStream } from "fs";
9
+ import { tmpdir } from "os";
10
+ import { join } from "path";
11
+ import { randomBytes } from "crypto";
12
+
13
+ // src/capabilities/bash/truncate.ts
14
+ var DEFAULT_MAX_LINES = 2e3;
15
+ var DEFAULT_MAX_BYTES = 50 * 1024;
16
+ function formatSize(bytes) {
17
+ if (bytes < 1024) {
18
+ return `${bytes}B`;
19
+ } else if (bytes < 1024 * 1024) {
20
+ return `${(bytes / 1024).toFixed(1)}KB`;
21
+ } else {
22
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
23
+ }
24
+ }
25
+ function truncateTail(content, options = {}) {
26
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
27
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
28
+ const totalBytes = Buffer.byteLength(content, "utf-8");
29
+ const lines = content.split("\n");
30
+ const totalLines = lines.length;
31
+ if (totalLines <= maxLines && totalBytes <= maxBytes) {
32
+ return {
33
+ content,
34
+ truncated: false,
35
+ truncatedBy: null,
36
+ totalLines,
37
+ totalBytes,
38
+ outputLines: totalLines,
39
+ outputBytes: totalBytes,
40
+ lastLinePartial: false,
41
+ maxLines,
42
+ maxBytes
43
+ };
44
+ }
45
+ const outputLinesArr = [];
46
+ let outputBytesCount = 0;
47
+ let truncatedBy = "lines";
48
+ let lastLinePartial = false;
49
+ for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
50
+ const line = lines[i];
51
+ const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0);
52
+ if (outputBytesCount + lineBytes > maxBytes) {
53
+ truncatedBy = "bytes";
54
+ if (outputLinesArr.length === 0) {
55
+ const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
56
+ outputLinesArr.unshift(truncatedLine);
57
+ outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
58
+ lastLinePartial = true;
59
+ }
60
+ break;
61
+ }
62
+ outputLinesArr.unshift(line);
63
+ outputBytesCount += lineBytes;
64
+ }
65
+ if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
66
+ truncatedBy = "lines";
67
+ }
68
+ const outputContent = outputLinesArr.join("\n");
69
+ const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
70
+ return {
71
+ content: outputContent,
72
+ truncated: true,
73
+ truncatedBy,
74
+ totalLines,
75
+ totalBytes,
76
+ outputLines: outputLinesArr.length,
77
+ outputBytes: finalOutputBytes,
78
+ lastLinePartial,
79
+ maxLines,
80
+ maxBytes
81
+ };
82
+ }
83
+ function truncateStringToBytesFromEnd(str, maxBytes) {
84
+ const buf = Buffer.from(str, "utf-8");
85
+ if (buf.length <= maxBytes) {
86
+ return str;
87
+ }
88
+ let start = buf.length - maxBytes;
89
+ while (start < buf.length && (buf[start] & 192) === 128) {
90
+ start++;
91
+ }
92
+ return buf.slice(start).toString("utf-8");
93
+ }
94
+
95
+ // src/capabilities/bash/index.ts
96
+ var bashSchema = Type.Object({
97
+ command: Type.String({ description: "The bash command to execute" }),
98
+ timeout: Type.Optional(
99
+ Type.Number({
100
+ description: "Timeout in seconds. Default: 120s (2 minutes). For longer tasks, explicitly set a higher value: { timeout: 600 } for npm install, { timeout: 600 } for cargo build, etc."
101
+ })
102
+ ),
103
+ cwd: Type.Optional(Type.String({ description: "Working directory for the command" }))
104
+ });
105
+ var DEFAULT_TIMEOUT_SECONDS = 120;
106
+ function getTempFilePath() {
107
+ const id = randomBytes(8).toString("hex");
108
+ return join(tmpdir(), `pie-bash-${id}.log`);
109
+ }
110
+ function killProcessTree(pid) {
111
+ if (process.platform === "win32") {
112
+ try {
113
+ spawn("taskkill", ["/F", "/T", "/PID", String(pid)], {
114
+ stdio: "ignore",
115
+ detached: true
116
+ });
117
+ } catch {
118
+ }
119
+ } else {
120
+ try {
121
+ process.kill(-pid, "SIGKILL");
122
+ } catch {
123
+ try {
124
+ process.kill(pid, "SIGKILL");
125
+ } catch {
126
+ }
127
+ }
128
+ }
129
+ }
130
+ function createBashTool(defaultCwd) {
131
+ return {
132
+ name: "bash",
133
+ label: "bash",
134
+ description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Default timeout: 120s. Use timeout parameter for longer tasks (e.g., npm install -> 600).`,
135
+ parameters: bashSchema,
136
+ execute: async (_toolCallId, { command, timeout, cwd }, signal, onUpdate) => {
137
+ const workingDir = cwd ?? defaultCwd ?? process.cwd();
138
+ const timeoutMs = (timeout ?? DEFAULT_TIMEOUT_SECONDS) * 1e3;
139
+ return new Promise((resolve, reject) => {
140
+ if (signal?.aborted) {
141
+ reject(new Error("Operation aborted"));
142
+ return;
143
+ }
144
+ let tempFilePath;
145
+ let tempFileStream;
146
+ let totalBytes = 0;
147
+ const chunks = [];
148
+ let chunksBytes = 0;
149
+ const maxChunksBytes = DEFAULT_MAX_BYTES * 2;
150
+ let timedOut = false;
151
+ const child = spawn(command, [], {
152
+ shell: true,
153
+ cwd: workingDir,
154
+ env: process.env,
155
+ detached: true
156
+ // Allows killing entire process tree
157
+ });
158
+ const timeoutId = setTimeout(() => {
159
+ timedOut = true;
160
+ if (child.pid) {
161
+ killProcessTree(child.pid);
162
+ }
163
+ }, timeoutMs);
164
+ const onAbort = () => {
165
+ clearTimeout(timeoutId);
166
+ if (child.pid) {
167
+ killProcessTree(child.pid);
168
+ }
169
+ reject(new Error("Operation aborted"));
170
+ };
171
+ signal?.addEventListener("abort", onAbort, { once: true });
172
+ child.stdout?.on("data", (data) => {
173
+ totalBytes += data.length;
174
+ if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
175
+ tempFilePath = getTempFilePath();
176
+ tempFileStream = createWriteStream(tempFilePath);
177
+ for (const chunk of chunks) {
178
+ tempFileStream.write(chunk);
179
+ }
180
+ }
181
+ if (tempFileStream) {
182
+ tempFileStream.write(data);
183
+ }
184
+ chunks.push(data);
185
+ chunksBytes += data.length;
186
+ while (chunksBytes > maxChunksBytes && chunks.length > 1) {
187
+ const removed = chunks.shift();
188
+ chunksBytes -= removed.length;
189
+ }
190
+ if (onUpdate) {
191
+ const fullBuffer = Buffer.concat(chunks);
192
+ const fullText = fullBuffer.toString("utf-8");
193
+ const truncation = truncateTail(fullText);
194
+ onUpdate({
195
+ content: [{ type: "text", text: truncation.content || "" }],
196
+ details: {
197
+ exitCode: null,
198
+ signal: null,
199
+ timedOut: false,
200
+ truncation: truncation.truncated ? {
201
+ truncated: true,
202
+ truncatedBy: truncation.truncatedBy,
203
+ totalLines: truncation.totalLines,
204
+ outputLines: truncation.outputLines
205
+ } : void 0,
206
+ fullOutputPath: tempFilePath
207
+ }
208
+ });
209
+ }
210
+ });
211
+ child.stderr?.on("data", (data) => {
212
+ totalBytes += data.length;
213
+ if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
214
+ tempFilePath = getTempFilePath();
215
+ tempFileStream = createWriteStream(tempFilePath);
216
+ for (const chunk of chunks) {
217
+ tempFileStream.write(chunk);
218
+ }
219
+ }
220
+ if (tempFileStream) {
221
+ tempFileStream.write(data);
222
+ }
223
+ chunks.push(data);
224
+ chunksBytes += data.length;
225
+ while (chunksBytes > maxChunksBytes && chunks.length > 1) {
226
+ const removed = chunks.shift();
227
+ chunksBytes -= removed.length;
228
+ }
229
+ });
230
+ child.on("close", (code, sig) => {
231
+ clearTimeout(timeoutId);
232
+ signal?.removeEventListener("abort", onAbort);
233
+ if (tempFileStream) {
234
+ tempFileStream.end();
235
+ }
236
+ const fullBuffer = Buffer.concat(chunks);
237
+ let fullOutput = fullBuffer.toString("utf-8");
238
+ const truncation = truncateTail(fullOutput);
239
+ let outputText = truncation.content || "(no output)";
240
+ const details = {
241
+ exitCode: code,
242
+ signal: sig?.toString() ?? null,
243
+ timedOut
244
+ };
245
+ if (truncation.truncated) {
246
+ details.truncation = {
247
+ truncated: true,
248
+ truncatedBy: truncation.truncatedBy,
249
+ totalLines: truncation.totalLines,
250
+ outputLines: truncation.outputLines
251
+ };
252
+ details.fullOutputPath = tempFilePath;
253
+ const startLine = truncation.totalLines - truncation.outputLines + 1;
254
+ const endLine = truncation.totalLines;
255
+ if (truncation.lastLinePartial) {
256
+ const lastLineSize = formatSize(
257
+ Buffer.byteLength(fullOutput.split("\n").pop() || "", "utf-8")
258
+ );
259
+ outputText += `
260
+
261
+ [Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;
262
+ } else if (truncation.truncatedBy === "lines") {
263
+ outputText += `
264
+
265
+ [Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;
266
+ } else {
267
+ outputText += `
268
+
269
+ [Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;
270
+ }
271
+ }
272
+ if (timedOut) {
273
+ outputText += `
274
+
275
+ [Command timed out after ${timeout ?? DEFAULT_TIMEOUT_SECONDS} seconds]`;
276
+ }
277
+ const content = [{ type: "text", text: outputText }];
278
+ if (code !== 0 && code !== null) {
279
+ reject(new Error(outputText));
280
+ } else {
281
+ resolve({ content, details });
282
+ }
283
+ });
284
+ child.on("error", (err) => {
285
+ clearTimeout(timeoutId);
286
+ signal?.removeEventListener("abort", onAbort);
287
+ if (tempFileStream) {
288
+ tempFileStream.end();
289
+ }
290
+ reject(err);
291
+ });
292
+ });
293
+ }
294
+ };
295
+ }
296
+
297
+ // src/capabilities/index.ts
298
+ function createCliHostCapabilities(cwd) {
299
+ const bashTool = createBashTool(cwd);
300
+ const capabilities = [
301
+ {
302
+ id: "bash",
303
+ description: "Node/TUI host capability for executing shell commands in the local workspace.",
304
+ tools: [bashTool]
305
+ }
306
+ ];
307
+ return {
308
+ capabilities,
309
+ tools: capabilities.flatMap((capability) => capability.tools)
310
+ };
311
+ }
312
+
313
+ export {
314
+ createCliHostCapabilities
315
+ };