@aexol/spectral 0.7.1 → 0.7.5

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 (219) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/dist/agent/agents.js +1 -1
  3. package/dist/agent/index.js +199 -184
  4. package/dist/commands/serve.js +0 -3
  5. package/dist/designer/data/systems/renault/DESIGN.md +1 -1
  6. package/dist/designer/philosophies.js +668 -0
  7. package/dist/mcp/sampling-handler.js +1 -1
  8. package/dist/memory/commands/status.js +1 -1
  9. package/dist/memory/compaction.js +2 -2
  10. package/dist/memory/config.js +1 -1
  11. package/dist/memory/debug-log.js +1 -1
  12. package/dist/memory/hooks/compaction-hook.js +29 -0
  13. package/dist/memory/index.js +2 -0
  14. package/dist/memory/observer.js +2 -2
  15. package/dist/memory/project-observations-store.js +14 -0
  16. package/dist/memory/tokens.js +1 -1
  17. package/dist/memory/tools/read-project-observations.js +82 -0
  18. package/dist/memory/tools/recall-observation.js +2 -2
  19. package/dist/pi/agent-core/agent-loop.js +501 -0
  20. package/dist/pi/agent-core/agent.js +401 -0
  21. package/dist/pi/agent-core/harness/agent-harness.js +899 -0
  22. package/dist/pi/agent-core/harness/compaction/branch-summarization.js +173 -0
  23. package/dist/pi/agent-core/harness/compaction/compaction.js +532 -0
  24. package/dist/pi/agent-core/harness/compaction/utils.js +130 -0
  25. package/dist/pi/agent-core/harness/env/nodejs.js +485 -0
  26. package/dist/pi/agent-core/harness/messages.js +101 -0
  27. package/dist/pi/agent-core/harness/prompt-templates.js +229 -0
  28. package/dist/pi/agent-core/harness/session/jsonl-repo.js +100 -0
  29. package/dist/pi/agent-core/harness/session/jsonl-storage.js +230 -0
  30. package/dist/pi/agent-core/harness/session/memory-repo.js +41 -0
  31. package/dist/pi/agent-core/harness/session/memory-storage.js +113 -0
  32. package/dist/pi/agent-core/harness/session/repo-utils.js +38 -0
  33. package/dist/pi/agent-core/harness/session/session.js +196 -0
  34. package/dist/pi/agent-core/harness/session/uuid.js +49 -0
  35. package/dist/pi/agent-core/harness/skills.js +310 -0
  36. package/dist/pi/agent-core/harness/system-prompt.js +29 -0
  37. package/dist/pi/agent-core/harness/types.js +93 -0
  38. package/dist/pi/agent-core/harness/utils/shell-output.js +125 -0
  39. package/dist/pi/agent-core/harness/utils/truncate.js +289 -0
  40. package/dist/pi/agent-core/index.js +24 -0
  41. package/dist/pi/agent-core/node.js +2 -0
  42. package/dist/pi/agent-core/proxy.js +277 -0
  43. package/dist/pi/agent-core/types.js +1 -0
  44. package/dist/pi/ai/api-registry.js +43 -0
  45. package/dist/pi/ai/cli.js +120 -0
  46. package/dist/pi/ai/env-api-keys.js +169 -0
  47. package/dist/pi/ai/image-models.generated.js +441 -0
  48. package/dist/pi/ai/image-models.js +22 -0
  49. package/dist/pi/ai/images-api-registry.js +21 -0
  50. package/dist/pi/ai/images.js +13 -0
  51. package/dist/pi/ai/index.js +18 -0
  52. package/dist/pi/ai/models.generated.js +16220 -0
  53. package/dist/pi/ai/models.js +70 -0
  54. package/dist/pi/ai/oauth.js +1 -0
  55. package/dist/pi/ai/providers/anthropic.js +945 -0
  56. package/dist/pi/ai/providers/faux.js +367 -0
  57. package/dist/pi/ai/providers/github-copilot-headers.js +28 -0
  58. package/dist/pi/ai/providers/openai-completions.js +945 -0
  59. package/dist/pi/ai/providers/openai-prompt-cache.js +9 -0
  60. package/dist/pi/ai/providers/register-builtins.js +97 -0
  61. package/dist/pi/ai/providers/simple-options.js +40 -0
  62. package/dist/pi/ai/providers/transform-messages.js +183 -0
  63. package/dist/pi/ai/session-resources.js +21 -0
  64. package/dist/pi/ai/stream.js +26 -0
  65. package/dist/pi/ai/types.js +1 -0
  66. package/dist/pi/ai/utils/diagnostics.js +24 -0
  67. package/dist/pi/ai/utils/event-stream.js +80 -0
  68. package/dist/pi/ai/utils/hash.js +13 -0
  69. package/dist/pi/ai/utils/headers.js +7 -0
  70. package/dist/pi/ai/utils/json-parse.js +112 -0
  71. package/dist/pi/ai/utils/node-http-proxy.js +96 -0
  72. package/dist/pi/ai/utils/oauth/anthropic.js +334 -0
  73. package/dist/pi/ai/utils/oauth/device-code.js +54 -0
  74. package/dist/pi/ai/utils/oauth/github-copilot.js +270 -0
  75. package/dist/pi/ai/utils/oauth/index.js +121 -0
  76. package/dist/pi/ai/utils/oauth/oauth-page.js +104 -0
  77. package/dist/pi/ai/utils/oauth/openai-codex.js +384 -0
  78. package/dist/pi/ai/utils/oauth/pkce.js +30 -0
  79. package/dist/pi/ai/utils/oauth/types.js +1 -0
  80. package/dist/pi/ai/utils/overflow.js +150 -0
  81. package/dist/pi/ai/utils/sanitize-unicode.js +25 -0
  82. package/dist/pi/ai/utils/typebox-helpers.js +20 -0
  83. package/dist/pi/ai/utils/validation.js +280 -0
  84. package/dist/pi/coding-agent/bun/cli.js +7 -0
  85. package/dist/pi/coding-agent/bun/restore-sandbox-env.js +31 -0
  86. package/dist/pi/coding-agent/cli/args.js +340 -0
  87. package/dist/pi/coding-agent/cli/file-processor.js +82 -0
  88. package/dist/pi/coding-agent/cli/initial-message.js +21 -0
  89. package/dist/pi/coding-agent/cli.js +17 -0
  90. package/dist/pi/coding-agent/config.js +414 -0
  91. package/dist/pi/coding-agent/core/agent-session-runtime.js +299 -0
  92. package/dist/pi/coding-agent/core/agent-session-services.js +117 -0
  93. package/dist/pi/coding-agent/core/agent-session.js +2498 -0
  94. package/dist/pi/coding-agent/core/auth-guidance.js +20 -0
  95. package/dist/pi/coding-agent/core/auth-storage.js +441 -0
  96. package/dist/pi/coding-agent/core/bash-executor.js +110 -0
  97. package/dist/pi/coding-agent/core/compaction/branch-summarization.js +242 -0
  98. package/dist/pi/coding-agent/core/compaction/compaction.js +624 -0
  99. package/dist/pi/coding-agent/core/compaction/index.js +6 -0
  100. package/dist/pi/coding-agent/core/compaction/utils.js +152 -0
  101. package/dist/pi/coding-agent/core/defaults.js +1 -0
  102. package/dist/pi/coding-agent/core/diagnostics.js +1 -0
  103. package/dist/pi/coding-agent/core/event-bus.js +24 -0
  104. package/dist/pi/coding-agent/core/exec.js +74 -0
  105. package/dist/pi/coding-agent/core/export-html/ansi-to-html.js +248 -0
  106. package/dist/pi/coding-agent/core/export-html/index.js +225 -0
  107. package/dist/pi/coding-agent/core/export-html/tool-renderer.js +107 -0
  108. package/dist/pi/coding-agent/core/extensions/index.js +8 -0
  109. package/dist/pi/coding-agent/core/extensions/loader.js +485 -0
  110. package/dist/pi/coding-agent/core/extensions/runner.js +824 -0
  111. package/dist/pi/coding-agent/core/extensions/types.js +44 -0
  112. package/dist/pi/coding-agent/core/extensions/wrapper.js +21 -0
  113. package/dist/pi/coding-agent/core/footer-data-provider.js +309 -0
  114. package/dist/pi/coding-agent/core/http-dispatcher.js +47 -0
  115. package/dist/pi/coding-agent/core/index.js +11 -0
  116. package/dist/pi/coding-agent/core/keybindings.js +294 -0
  117. package/dist/pi/coding-agent/core/messages.js +122 -0
  118. package/dist/pi/coding-agent/core/model-registry.js +728 -0
  119. package/dist/pi/coding-agent/core/model-resolver.js +494 -0
  120. package/dist/pi/coding-agent/core/output-guard.js +58 -0
  121. package/dist/pi/coding-agent/core/package-manager.js +2020 -0
  122. package/dist/pi/coding-agent/core/prompt-templates.js +237 -0
  123. package/dist/pi/coding-agent/core/provider-display-names.js +32 -0
  124. package/dist/pi/coding-agent/core/resolve-config-value.js +125 -0
  125. package/dist/pi/coding-agent/core/resource-loader.js +733 -0
  126. package/dist/pi/coding-agent/core/sdk.js +282 -0
  127. package/dist/pi/coding-agent/core/session-cwd.js +37 -0
  128. package/dist/pi/coding-agent/core/session-manager.js +1146 -0
  129. package/dist/pi/coding-agent/core/settings-manager.js +794 -0
  130. package/dist/pi/coding-agent/core/skills.js +386 -0
  131. package/dist/pi/coding-agent/core/slash-commands.js +24 -0
  132. package/dist/pi/coding-agent/core/source-info.js +18 -0
  133. package/dist/pi/coding-agent/core/system-prompt.js +122 -0
  134. package/dist/pi/coding-agent/core/telemetry.js +8 -0
  135. package/dist/pi/coding-agent/core/timings.js +30 -0
  136. package/dist/pi/coding-agent/core/tools/bash.js +341 -0
  137. package/dist/pi/coding-agent/core/tools/edit-diff.js +344 -0
  138. package/dist/pi/coding-agent/core/tools/edit.js +324 -0
  139. package/dist/pi/coding-agent/core/tools/file-mutation-queue.js +36 -0
  140. package/dist/pi/coding-agent/core/tools/find.js +297 -0
  141. package/dist/pi/coding-agent/core/tools/grep.js +303 -0
  142. package/dist/pi/coding-agent/core/tools/index.js +111 -0
  143. package/dist/pi/coding-agent/core/tools/ls.js +168 -0
  144. package/dist/pi/coding-agent/core/tools/output-accumulator.js +183 -0
  145. package/dist/pi/coding-agent/core/tools/path-utils.js +61 -0
  146. package/dist/pi/coding-agent/core/tools/read.js +288 -0
  147. package/dist/pi/coding-agent/core/tools/render-utils.js +48 -0
  148. package/dist/pi/coding-agent/core/tools/tool-definition-wrapper.js +33 -0
  149. package/dist/pi/coding-agent/core/tools/truncate.js +214 -0
  150. package/dist/pi/coding-agent/core/tools/write.js +212 -0
  151. package/dist/pi/coding-agent/index.js +41 -0
  152. package/dist/pi/coding-agent/main.js +5 -0
  153. package/dist/pi/coding-agent/migrations.js +280 -0
  154. package/dist/pi/coding-agent/modes/index.js +7 -0
  155. package/dist/pi/coding-agent/modes/interactive/components/diff.js +132 -0
  156. package/dist/pi/coding-agent/modes/interactive/components/keybinding-hints.js +35 -0
  157. package/dist/pi/coding-agent/modes/interactive/components/visual-truncate.js +32 -0
  158. package/dist/pi/coding-agent/modes/interactive/interactive-mode.js +3 -0
  159. package/dist/pi/coding-agent/modes/interactive/theme/theme.js +1023 -0
  160. package/dist/pi/coding-agent/modes/print-mode.js +130 -0
  161. package/dist/pi/coding-agent/modes/rpc/jsonl.js +48 -0
  162. package/dist/pi/coding-agent/modes/rpc/rpc-client.js +409 -0
  163. package/dist/pi/coding-agent/modes/rpc/rpc-mode.js +600 -0
  164. package/dist/pi/coding-agent/modes/rpc/rpc-types.js +7 -0
  165. package/dist/pi/coding-agent/utils/ansi.js +51 -0
  166. package/dist/pi/coding-agent/utils/changelog.js +86 -0
  167. package/dist/pi/coding-agent/utils/child-process.js +87 -0
  168. package/dist/pi/coding-agent/utils/clipboard-image.js +244 -0
  169. package/dist/pi/coding-agent/utils/clipboard-native.js +13 -0
  170. package/dist/pi/coding-agent/utils/clipboard.js +116 -0
  171. package/dist/pi/coding-agent/utils/exif-orientation.js +157 -0
  172. package/dist/pi/coding-agent/utils/frontmatter.js +25 -0
  173. package/dist/pi/coding-agent/utils/fs-watch.js +24 -0
  174. package/dist/pi/coding-agent/utils/git.js +162 -0
  175. package/dist/pi/coding-agent/utils/html.js +39 -0
  176. package/dist/pi/coding-agent/utils/image-convert.js +38 -0
  177. package/dist/pi/coding-agent/utils/image-resize.js +136 -0
  178. package/dist/pi/coding-agent/utils/mime.js +68 -0
  179. package/dist/pi/coding-agent/utils/paths.js +91 -0
  180. package/dist/pi/coding-agent/utils/photon.js +120 -0
  181. package/dist/pi/coding-agent/utils/pi-user-agent.js +4 -0
  182. package/dist/pi/coding-agent/utils/shell.js +194 -0
  183. package/dist/pi/coding-agent/utils/sleep.js +16 -0
  184. package/dist/pi/coding-agent/utils/syntax-highlight.js +117 -0
  185. package/dist/pi/coding-agent/utils/tools-manager.js +327 -0
  186. package/dist/pi/coding-agent/utils/version-check.js +81 -0
  187. package/dist/pi/coding-agent/utils/windows-self-update.js +76 -0
  188. package/dist/pi/tui/autocomplete.js +631 -0
  189. package/dist/pi/tui/components/box.js +103 -0
  190. package/dist/pi/tui/components/cancellable-loader.js +34 -0
  191. package/dist/pi/tui/components/editor.js +1915 -0
  192. package/dist/pi/tui/components/image.js +88 -0
  193. package/dist/pi/tui/components/input.js +425 -0
  194. package/dist/pi/tui/components/loader.js +68 -0
  195. package/dist/pi/tui/components/markdown.js +633 -0
  196. package/dist/pi/tui/components/select-list.js +158 -0
  197. package/dist/pi/tui/components/settings-list.js +184 -0
  198. package/dist/pi/tui/components/spacer.js +22 -0
  199. package/dist/pi/tui/components/text.js +88 -0
  200. package/dist/pi/tui/components/truncated-text.js +50 -0
  201. package/dist/pi/tui/editor-component.js +1 -0
  202. package/dist/pi/tui/fuzzy.js +109 -0
  203. package/dist/pi/tui/index.js +31 -0
  204. package/dist/pi/tui/keybindings.js +173 -0
  205. package/dist/pi/tui/keys.js +1172 -0
  206. package/dist/pi/tui/kill-ring.js +43 -0
  207. package/dist/pi/tui/stdin-buffer.js +360 -0
  208. package/dist/pi/tui/terminal-image.js +335 -0
  209. package/dist/pi/tui/terminal.js +324 -0
  210. package/dist/pi/tui/tui.js +1076 -0
  211. package/dist/pi/tui/undo-stack.js +24 -0
  212. package/dist/pi/tui/utils.js +1016 -0
  213. package/dist/relay/dispatcher.js +30 -0
  214. package/dist/server/handlers/queue.js +52 -0
  215. package/dist/server/pi-bridge.js +9 -1
  216. package/dist/server/session-stream.js +76 -111
  217. package/dist/server/storage.js +154 -2
  218. package/dist/server/title-generator.js +14 -153
  219. package/package.json +24 -6
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Shared truncation utilities for tool outputs.
3
+ *
4
+ * Truncation is based on two independent limits - whichever is hit first wins:
5
+ * - Line limit (default: 2000 lines)
6
+ * - Byte limit (default: 50KB)
7
+ *
8
+ * Never returns partial lines (except bash tail truncation edge case).
9
+ */
10
+ export const DEFAULT_MAX_LINES = 2000;
11
+ export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
12
+ export const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line
13
+ function splitLinesForCounting(content) {
14
+ if (content.length === 0) {
15
+ return [];
16
+ }
17
+ const lines = content.split("\n");
18
+ if (content.endsWith("\n")) {
19
+ lines.pop();
20
+ }
21
+ return lines;
22
+ }
23
+ /**
24
+ * Format bytes as human-readable size.
25
+ */
26
+ export function formatSize(bytes) {
27
+ if (bytes < 1024) {
28
+ return `${bytes}B`;
29
+ }
30
+ else if (bytes < 1024 * 1024) {
31
+ return `${(bytes / 1024).toFixed(1)}KB`;
32
+ }
33
+ else {
34
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
35
+ }
36
+ }
37
+ /**
38
+ * Truncate content from the head (keep first N lines/bytes).
39
+ * Suitable for file reads where you want to see the beginning.
40
+ *
41
+ * Never returns partial lines. If first line exceeds byte limit,
42
+ * returns empty content with firstLineExceedsLimit=true.
43
+ */
44
+ export function truncateHead(content, options = {}) {
45
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
46
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
47
+ const totalBytes = Buffer.byteLength(content, "utf-8");
48
+ const lines = splitLinesForCounting(content);
49
+ const totalLines = lines.length;
50
+ // Check if no truncation needed
51
+ if (totalLines <= maxLines && totalBytes <= maxBytes) {
52
+ return {
53
+ content,
54
+ truncated: false,
55
+ truncatedBy: null,
56
+ totalLines,
57
+ totalBytes,
58
+ outputLines: totalLines,
59
+ outputBytes: totalBytes,
60
+ lastLinePartial: false,
61
+ firstLineExceedsLimit: false,
62
+ maxLines,
63
+ maxBytes,
64
+ };
65
+ }
66
+ // Check if first line alone exceeds byte limit
67
+ const firstLineBytes = Buffer.byteLength(lines[0], "utf-8");
68
+ if (firstLineBytes > maxBytes) {
69
+ return {
70
+ content: "",
71
+ truncated: true,
72
+ truncatedBy: "bytes",
73
+ totalLines,
74
+ totalBytes,
75
+ outputLines: 0,
76
+ outputBytes: 0,
77
+ lastLinePartial: false,
78
+ firstLineExceedsLimit: true,
79
+ maxLines,
80
+ maxBytes,
81
+ };
82
+ }
83
+ // Collect complete lines that fit
84
+ const outputLinesArr = [];
85
+ let outputBytesCount = 0;
86
+ let truncatedBy = "lines";
87
+ for (let i = 0; i < lines.length && i < maxLines; i++) {
88
+ const line = lines[i];
89
+ const lineBytes = Buffer.byteLength(line, "utf-8") + (i > 0 ? 1 : 0); // +1 for newline
90
+ if (outputBytesCount + lineBytes > maxBytes) {
91
+ truncatedBy = "bytes";
92
+ break;
93
+ }
94
+ outputLinesArr.push(line);
95
+ outputBytesCount += lineBytes;
96
+ }
97
+ // If we exited due to line limit
98
+ if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
99
+ truncatedBy = "lines";
100
+ }
101
+ const outputContent = outputLinesArr.join("\n");
102
+ const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
103
+ return {
104
+ content: outputContent,
105
+ truncated: true,
106
+ truncatedBy,
107
+ totalLines,
108
+ totalBytes,
109
+ outputLines: outputLinesArr.length,
110
+ outputBytes: finalOutputBytes,
111
+ lastLinePartial: false,
112
+ firstLineExceedsLimit: false,
113
+ maxLines,
114
+ maxBytes,
115
+ };
116
+ }
117
+ /**
118
+ * Truncate content from the tail (keep last N lines/bytes).
119
+ * Suitable for bash output where you want to see the end (errors, final results).
120
+ *
121
+ * May return partial first line if the last line of original content exceeds byte limit.
122
+ */
123
+ export function truncateTail(content, options = {}) {
124
+ const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
125
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
126
+ const totalBytes = Buffer.byteLength(content, "utf-8");
127
+ const lines = splitLinesForCounting(content);
128
+ const totalLines = lines.length;
129
+ // Check if no truncation needed
130
+ if (totalLines <= maxLines && totalBytes <= maxBytes) {
131
+ return {
132
+ content,
133
+ truncated: false,
134
+ truncatedBy: null,
135
+ totalLines,
136
+ totalBytes,
137
+ outputLines: totalLines,
138
+ outputBytes: totalBytes,
139
+ lastLinePartial: false,
140
+ firstLineExceedsLimit: false,
141
+ maxLines,
142
+ maxBytes,
143
+ };
144
+ }
145
+ // Work backwards from the end
146
+ const outputLinesArr = [];
147
+ let outputBytesCount = 0;
148
+ let truncatedBy = "lines";
149
+ let lastLinePartial = false;
150
+ for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
151
+ const line = lines[i];
152
+ const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline
153
+ if (outputBytesCount + lineBytes > maxBytes) {
154
+ truncatedBy = "bytes";
155
+ // Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,
156
+ // take the end of the line (partial)
157
+ if (outputLinesArr.length === 0) {
158
+ const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
159
+ outputLinesArr.unshift(truncatedLine);
160
+ outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
161
+ lastLinePartial = true;
162
+ }
163
+ break;
164
+ }
165
+ outputLinesArr.unshift(line);
166
+ outputBytesCount += lineBytes;
167
+ }
168
+ // If we exited due to line limit
169
+ if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
170
+ truncatedBy = "lines";
171
+ }
172
+ const outputContent = outputLinesArr.join("\n");
173
+ const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
174
+ return {
175
+ content: outputContent,
176
+ truncated: true,
177
+ truncatedBy,
178
+ totalLines,
179
+ totalBytes,
180
+ outputLines: outputLinesArr.length,
181
+ outputBytes: finalOutputBytes,
182
+ lastLinePartial,
183
+ firstLineExceedsLimit: false,
184
+ maxLines,
185
+ maxBytes,
186
+ };
187
+ }
188
+ /**
189
+ * Truncate a string to fit within a byte limit (from the end).
190
+ * Handles multi-byte UTF-8 characters correctly.
191
+ */
192
+ function truncateStringToBytesFromEnd(str, maxBytes) {
193
+ const buf = Buffer.from(str, "utf-8");
194
+ if (buf.length <= maxBytes) {
195
+ return str;
196
+ }
197
+ // Start from the end, skip maxBytes back
198
+ let start = buf.length - maxBytes;
199
+ // Find a valid UTF-8 boundary (start of a character)
200
+ while (start < buf.length && (buf[start] & 0xc0) === 0x80) {
201
+ start++;
202
+ }
203
+ return buf.slice(start).toString("utf-8");
204
+ }
205
+ /**
206
+ * Truncate a single line to max characters, adding [truncated] suffix.
207
+ * Used for grep match lines.
208
+ */
209
+ export function truncateLine(line, maxChars = GREP_MAX_LINE_LENGTH) {
210
+ if (line.length <= maxChars) {
211
+ return { text: line, wasTruncated: false };
212
+ }
213
+ return { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };
214
+ }
@@ -0,0 +1,212 @@
1
+ import { Container, Text } from "../../../tui/index.js";
2
+ import { mkdir as fsMkdir, writeFile as fsWriteFile } from "fs/promises";
3
+ import { dirname } from "path";
4
+ import { Type } from "typebox";
5
+ import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
6
+ import { getLanguageFromPath, highlightCode } from "../../modes/interactive/theme/theme.js";
7
+ import { withFileMutationQueue } from "./file-mutation-queue.js";
8
+ import { resolveToCwd } from "./path-utils.js";
9
+ import { invalidArgText, normalizeDisplayText, replaceTabs, shortenPath, str } from "./render-utils.js";
10
+ import { wrapToolDefinition } from "./tool-definition-wrapper.js";
11
+ const writeSchema = Type.Object({
12
+ path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
13
+ content: Type.String({ description: "Content to write to the file" }),
14
+ });
15
+ const defaultWriteOperations = {
16
+ writeFile: (path, content) => fsWriteFile(path, content, "utf-8"),
17
+ mkdir: (dir) => fsMkdir(dir, { recursive: true }).then(() => { }),
18
+ };
19
+ class WriteCallRenderComponent extends Text {
20
+ cache;
21
+ constructor() {
22
+ super("", 0, 0);
23
+ }
24
+ }
25
+ const WRITE_PARTIAL_FULL_HIGHLIGHT_LINES = 50;
26
+ function highlightSingleLine(line, lang) {
27
+ const highlighted = highlightCode(line, lang);
28
+ return highlighted[0] ?? "";
29
+ }
30
+ function refreshWriteHighlightPrefix(cache) {
31
+ const prefixCount = Math.min(WRITE_PARTIAL_FULL_HIGHLIGHT_LINES, cache.normalizedLines.length);
32
+ if (prefixCount === 0)
33
+ return;
34
+ const prefixSource = cache.normalizedLines.slice(0, prefixCount).join("\n");
35
+ const prefixHighlighted = highlightCode(prefixSource, cache.lang);
36
+ for (let i = 0; i < prefixCount; i++) {
37
+ cache.highlightedLines[i] =
38
+ prefixHighlighted[i] ?? highlightSingleLine(cache.normalizedLines[i] ?? "", cache.lang);
39
+ }
40
+ }
41
+ function rebuildWriteHighlightCacheFull(rawPath, fileContent) {
42
+ const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
43
+ if (!lang)
44
+ return undefined;
45
+ const displayContent = normalizeDisplayText(fileContent);
46
+ const normalized = replaceTabs(displayContent);
47
+ return {
48
+ rawPath,
49
+ lang,
50
+ rawContent: fileContent,
51
+ normalizedLines: normalized.split("\n"),
52
+ highlightedLines: highlightCode(normalized, lang),
53
+ };
54
+ }
55
+ function updateWriteHighlightCacheIncremental(cache, rawPath, fileContent) {
56
+ const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
57
+ if (!lang)
58
+ return undefined;
59
+ if (!cache)
60
+ return rebuildWriteHighlightCacheFull(rawPath, fileContent);
61
+ if (cache.lang !== lang || cache.rawPath !== rawPath)
62
+ return rebuildWriteHighlightCacheFull(rawPath, fileContent);
63
+ if (!fileContent.startsWith(cache.rawContent))
64
+ return rebuildWriteHighlightCacheFull(rawPath, fileContent);
65
+ if (fileContent.length === cache.rawContent.length)
66
+ return cache;
67
+ const deltaRaw = fileContent.slice(cache.rawContent.length);
68
+ const deltaDisplay = normalizeDisplayText(deltaRaw);
69
+ const deltaNormalized = replaceTabs(deltaDisplay);
70
+ cache.rawContent = fileContent;
71
+ if (cache.normalizedLines.length === 0) {
72
+ cache.normalizedLines.push("");
73
+ cache.highlightedLines.push("");
74
+ }
75
+ const segments = deltaNormalized.split("\n");
76
+ const lastIndex = cache.normalizedLines.length - 1;
77
+ cache.normalizedLines[lastIndex] += segments[0];
78
+ cache.highlightedLines[lastIndex] = highlightSingleLine(cache.normalizedLines[lastIndex], cache.lang);
79
+ for (let i = 1; i < segments.length; i++) {
80
+ cache.normalizedLines.push(segments[i]);
81
+ cache.highlightedLines.push(highlightSingleLine(segments[i], cache.lang));
82
+ }
83
+ refreshWriteHighlightPrefix(cache);
84
+ return cache;
85
+ }
86
+ function trimTrailingEmptyLines(lines) {
87
+ let end = lines.length;
88
+ while (end > 0 && lines[end - 1] === "") {
89
+ end--;
90
+ }
91
+ return lines.slice(0, end);
92
+ }
93
+ function formatWriteCall(args, options, theme, cache) {
94
+ const rawPath = str(args?.file_path ?? args?.path);
95
+ const fileContent = str(args?.content);
96
+ const path = rawPath !== null ? shortenPath(rawPath) : null;
97
+ const invalidArg = invalidArgText(theme);
98
+ let text = `${theme.fg("toolTitle", theme.bold("write"))} ${path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...")}`;
99
+ if (fileContent === null) {
100
+ text += `\n\n${theme.fg("error", "[invalid content arg - expected string]")}`;
101
+ }
102
+ else if (fileContent) {
103
+ const lang = rawPath ? getLanguageFromPath(rawPath) : undefined;
104
+ const renderedLines = lang
105
+ ? (cache?.highlightedLines ?? highlightCode(replaceTabs(normalizeDisplayText(fileContent)), lang))
106
+ : normalizeDisplayText(fileContent).split("\n");
107
+ const lines = trimTrailingEmptyLines(renderedLines);
108
+ const totalLines = lines.length;
109
+ const maxLines = options.expanded ? lines.length : 10;
110
+ const displayLines = lines.slice(0, maxLines);
111
+ const remaining = lines.length - maxLines;
112
+ text += `\n\n${displayLines.map((line) => (lang ? line : theme.fg("toolOutput", replaceTabs(line)))).join("\n")}`;
113
+ if (remaining > 0) {
114
+ text += `${theme.fg("muted", `\n... (${remaining} more lines, ${totalLines} total,`)} ${keyHint("app.tools.expand", "to expand")})`;
115
+ }
116
+ }
117
+ return text;
118
+ }
119
+ function formatWriteResult(result, theme) {
120
+ if (!result.isError) {
121
+ return undefined;
122
+ }
123
+ const output = result.content
124
+ .filter((c) => c.type === "text")
125
+ .map((c) => c.text || "")
126
+ .join("\n");
127
+ if (!output) {
128
+ return undefined;
129
+ }
130
+ return `\n${theme.fg("error", output)}`;
131
+ }
132
+ export function createWriteToolDefinition(cwd, options) {
133
+ const ops = options?.operations ?? defaultWriteOperations;
134
+ return {
135
+ name: "write",
136
+ label: "write",
137
+ description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
138
+ promptSnippet: "Create or overwrite files",
139
+ promptGuidelines: ["Use write only for new files or complete rewrites."],
140
+ parameters: writeSchema,
141
+ async execute(_toolCallId, { path, content }, signal, _onUpdate, _ctx) {
142
+ const absolutePath = resolveToCwd(path, cwd);
143
+ const dir = dirname(absolutePath);
144
+ return withFileMutationQueue(absolutePath, () => new Promise((resolve, reject) => {
145
+ if (signal?.aborted) {
146
+ reject(new Error("Operation aborted"));
147
+ return;
148
+ }
149
+ let aborted = false;
150
+ const onAbort = () => {
151
+ aborted = true;
152
+ reject(new Error("Operation aborted"));
153
+ };
154
+ signal?.addEventListener("abort", onAbort, { once: true });
155
+ (async () => {
156
+ try {
157
+ // Create parent directories if needed.
158
+ await ops.mkdir(dir);
159
+ if (aborted)
160
+ return;
161
+ // Write the file contents.
162
+ await ops.writeFile(absolutePath, content);
163
+ if (aborted)
164
+ return;
165
+ signal?.removeEventListener("abort", onAbort);
166
+ resolve({
167
+ content: [
168
+ { type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` },
169
+ ],
170
+ details: undefined,
171
+ });
172
+ }
173
+ catch (error) {
174
+ signal?.removeEventListener("abort", onAbort);
175
+ if (!aborted)
176
+ reject(error);
177
+ }
178
+ })();
179
+ }));
180
+ },
181
+ renderCall(args, theme, context) {
182
+ const renderArgs = args;
183
+ const rawPath = str(renderArgs?.file_path ?? renderArgs?.path);
184
+ const fileContent = str(renderArgs?.content);
185
+ const component = context.lastComponent ?? new WriteCallRenderComponent();
186
+ if (fileContent !== null) {
187
+ component.cache = context.argsComplete
188
+ ? rebuildWriteHighlightCacheFull(rawPath, fileContent)
189
+ : updateWriteHighlightCacheIncremental(component.cache, rawPath, fileContent);
190
+ }
191
+ else {
192
+ component.cache = undefined;
193
+ }
194
+ component.setText(formatWriteCall(renderArgs, { expanded: context.expanded, isPartial: context.isPartial }, theme, component.cache));
195
+ return component;
196
+ },
197
+ renderResult(result, _options, theme, context) {
198
+ const output = formatWriteResult({ ...result, isError: context.isError }, theme);
199
+ if (!output) {
200
+ const component = context.lastComponent ?? new Container();
201
+ component.clear();
202
+ return component;
203
+ }
204
+ const text = context.lastComponent ?? new Text("", 0, 0);
205
+ text.setText(output);
206
+ return text;
207
+ },
208
+ };
209
+ }
210
+ export function createWriteTool(cwd, options) {
211
+ return wrapToolDefinition(createWriteToolDefinition(cwd, options));
212
+ }
@@ -0,0 +1,41 @@
1
+ // Core session management
2
+ // Config paths
3
+ export { getAgentDir, VERSION } from "./config.js";
4
+ export { AgentSession, parseSkillBlock, } from "./core/agent-session.js";
5
+ // Auth and model registry
6
+ export { AuthStorage, FileAuthStorageBackend, InMemoryAuthStorageBackend, } from "./core/auth-storage.js";
7
+ // Compaction
8
+ export { calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, findCutPoint, findTurnStartIndex, generateBranchSummary, generateSummary, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.js";
9
+ export { createEventBus } from "./core/event-bus.js";
10
+ export { createExtensionRuntime, defineTool, discoverAndLoadExtensions, ExtensionRunner, isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isReadToolResult, isToolCallEventType, isWriteToolResult, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.js";
11
+ export { convertToLlm } from "./core/messages.js";
12
+ export { ModelRegistry } from "./core/model-registry.js";
13
+ export { DefaultPackageManager } from "./core/package-manager.js";
14
+ export { DefaultResourceLoader, loadProjectContextFiles } from "./core/resource-loader.js";
15
+ // SDK for programmatic usage
16
+ export { AgentSessionRuntime,
17
+ // Factory
18
+ createAgentSession, createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, createBashTool,
19
+ // Tool factories (for custom cwd)
20
+ createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWriteTool, } from "./core/sdk.js";
21
+ export { buildSessionContext, CURRENT_SESSION_VERSION, getLatestCompactionEntry, migrateSessionEntries, parseSessionEntries, SessionManager, } from "./core/session-manager.js";
22
+ export { SettingsManager, } from "./core/settings-manager.js";
23
+ // Skills
24
+ export { formatSkillsForPrompt, loadSkills, loadSkillsFromDir, } from "./core/skills.js";
25
+ export { createSyntheticSourceInfo } from "./core/source-info.js";
26
+ // Tools
27
+ export { createBashToolDefinition, createEditToolDefinition, createFindToolDefinition, createGrepToolDefinition, createLocalBashOperations, createLsToolDefinition, createReadToolDefinition, createWriteToolDefinition, DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead, truncateLine, truncateTail, withFileMutationQueue, } from "./core/tools/index.js";
28
+ // Main entry point
29
+ // main.ts removed (interactive-only, not used in SDK mode)
30
+ // Run modes for programmatic SDK usage
31
+ export { InteractiveMode, RpcClient, runPrintMode, runRpcMode, } from "./modes/index.js";
32
+ // UI components for extensions
33
+ // Interactive components removed (not used in SDK mode, TUI dependencies stripped)
34
+ // Theme utilities for custom tools and extensions
35
+ export { getLanguageFromPath, getMarkdownTheme, getSelectListTheme, getSettingsListTheme, highlightCode, initTheme, Theme, } from "./modes/interactive/theme/theme.js";
36
+ // Clipboard utilities
37
+ export { copyToClipboard } from "./utils/clipboard.js";
38
+ export { parseFrontmatter, stripFrontmatter } from "./utils/frontmatter.js";
39
+ export { formatDimensionNote, resizeImage } from "./utils/image-resize.js";
40
+ // Shell utilities
41
+ export { getShellConfig } from "./utils/shell.js";
@@ -0,0 +1,5 @@
1
+ // CLI entry point removed (interactive-only, not used in SDK mode by spectral)
2
+ // Replaced with no-op to satisfy imports during build.
3
+ export function main(_args) {
4
+ throw new Error("Interactive CLI mode is not available in SDK-only build");
5
+ }