@compilr-dev/cli 0.4.0

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 (152) hide show
  1. package/README.md +110 -0
  2. package/dist/agent.d.ts +62 -0
  3. package/dist/agent.js +317 -0
  4. package/dist/agents/registry.d.ts +66 -0
  5. package/dist/agents/registry.js +238 -0
  6. package/dist/agents/types.d.ts +40 -0
  7. package/dist/agents/types.js +94 -0
  8. package/dist/commands/custom-registry.d.ts +69 -0
  9. package/dist/commands/custom-registry.js +246 -0
  10. package/dist/commands/index.d.ts +7 -0
  11. package/dist/commands/index.js +7 -0
  12. package/dist/commands/types.d.ts +31 -0
  13. package/dist/commands/types.js +26 -0
  14. package/dist/commands.d.ts +63 -0
  15. package/dist/commands.js +324 -0
  16. package/dist/db/index.d.ts +42 -0
  17. package/dist/db/index.js +146 -0
  18. package/dist/db/repositories/document-repository.d.ts +63 -0
  19. package/dist/db/repositories/document-repository.js +184 -0
  20. package/dist/db/repositories/index.d.ts +9 -0
  21. package/dist/db/repositories/index.js +6 -0
  22. package/dist/db/repositories/project-repository.d.ts +132 -0
  23. package/dist/db/repositories/project-repository.js +337 -0
  24. package/dist/db/repositories/work-item-repository.d.ts +115 -0
  25. package/dist/db/repositories/work-item-repository.js +389 -0
  26. package/dist/db/schema.d.ts +83 -0
  27. package/dist/db/schema.js +143 -0
  28. package/dist/debug.d.ts +8 -0
  29. package/dist/debug.js +48 -0
  30. package/dist/index.d.ts +2 -0
  31. package/dist/index.js +348 -0
  32. package/dist/index.old.d.ts +7 -0
  33. package/dist/index.old.js +1014 -0
  34. package/dist/repl.d.ts +121 -0
  35. package/dist/repl.js +1878 -0
  36. package/dist/settings/index.d.ts +80 -0
  37. package/dist/settings/index.js +195 -0
  38. package/dist/shared-handlers.d.ts +63 -0
  39. package/dist/shared-handlers.js +57 -0
  40. package/dist/slash-autocomplete.d.ts +41 -0
  41. package/dist/slash-autocomplete.js +638 -0
  42. package/dist/state.d.ts +75 -0
  43. package/dist/state.js +130 -0
  44. package/dist/tabbed-menu.d.ts +11 -0
  45. package/dist/tabbed-menu.js +328 -0
  46. package/dist/templates/backlog-md.d.ts +7 -0
  47. package/dist/templates/backlog-md.js +94 -0
  48. package/dist/templates/claude-md.d.ts +7 -0
  49. package/dist/templates/claude-md.js +189 -0
  50. package/dist/templates/coding-standards.d.ts +7 -0
  51. package/dist/templates/coding-standards.js +299 -0
  52. package/dist/templates/compilr-md.d.ts +7 -0
  53. package/dist/templates/compilr-md.js +189 -0
  54. package/dist/templates/config-json.d.ts +38 -0
  55. package/dist/templates/config-json.js +39 -0
  56. package/dist/templates/gitignore.d.ts +7 -0
  57. package/dist/templates/gitignore.js +85 -0
  58. package/dist/templates/index.d.ts +19 -0
  59. package/dist/templates/index.js +302 -0
  60. package/dist/templates/package-json.d.ts +7 -0
  61. package/dist/templates/package-json.js +111 -0
  62. package/dist/templates/readme-md.d.ts +7 -0
  63. package/dist/templates/readme-md.js +161 -0
  64. package/dist/templates/tsconfig.d.ts +7 -0
  65. package/dist/templates/tsconfig.js +61 -0
  66. package/dist/templates/types.d.ts +33 -0
  67. package/dist/templates/types.js +24 -0
  68. package/dist/test-autocomplete.d.ts +7 -0
  69. package/dist/test-autocomplete.js +85 -0
  70. package/dist/test-tabbed-menu.d.ts +7 -0
  71. package/dist/test-tabbed-menu.js +25 -0
  72. package/dist/themes/colors.d.ts +49 -0
  73. package/dist/themes/colors.js +135 -0
  74. package/dist/themes/index.d.ts +23 -0
  75. package/dist/themes/index.js +24 -0
  76. package/dist/themes/registry.d.ts +60 -0
  77. package/dist/themes/registry.js +195 -0
  78. package/dist/themes/types.d.ts +82 -0
  79. package/dist/themes/types.js +7 -0
  80. package/dist/tool-selector.d.ts +71 -0
  81. package/dist/tool-selector.js +184 -0
  82. package/dist/tools/ask-user-simple.d.ts +19 -0
  83. package/dist/tools/ask-user-simple.js +86 -0
  84. package/dist/tools/ask-user.d.ts +32 -0
  85. package/dist/tools/ask-user.js +113 -0
  86. package/dist/tools/backlog.d.ts +53 -0
  87. package/dist/tools/backlog.js +709 -0
  88. package/dist/tools.d.ts +15 -0
  89. package/dist/tools.js +121 -0
  90. package/dist/ui/agents-overlay.d.ts +12 -0
  91. package/dist/ui/agents-overlay.js +501 -0
  92. package/dist/ui/arch-type-overlay.d.ts +20 -0
  93. package/dist/ui/arch-type-overlay.js +229 -0
  94. package/dist/ui/ask-user-overlay.d.ts +26 -0
  95. package/dist/ui/ask-user-overlay.js +647 -0
  96. package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
  97. package/dist/ui/ask-user-simple-overlay.js +242 -0
  98. package/dist/ui/backlog-overlay.d.ts +17 -0
  99. package/dist/ui/backlog-overlay.js +786 -0
  100. package/dist/ui/commands-overlay.d.ts +11 -0
  101. package/dist/ui/commands-overlay.js +410 -0
  102. package/dist/ui/config-overlay.d.ts +34 -0
  103. package/dist/ui/config-overlay.js +977 -0
  104. package/dist/ui/conversation.d.ts +82 -0
  105. package/dist/ui/conversation.js +508 -0
  106. package/dist/ui/diff.d.ts +38 -0
  107. package/dist/ui/diff.js +182 -0
  108. package/dist/ui/ephemeral.d.ts +111 -0
  109. package/dist/ui/ephemeral.js +413 -0
  110. package/dist/ui/file-autocomplete.d.ts +45 -0
  111. package/dist/ui/file-autocomplete.js +237 -0
  112. package/dist/ui/footer.d.ts +153 -0
  113. package/dist/ui/footer.js +422 -0
  114. package/dist/ui/index.d.ts +12 -0
  115. package/dist/ui/index.js +15 -0
  116. package/dist/ui/init-overlay.d.ts +24 -0
  117. package/dist/ui/init-overlay.js +525 -0
  118. package/dist/ui/input-prompt-v2.d.ts +179 -0
  119. package/dist/ui/input-prompt-v2.js +991 -0
  120. package/dist/ui/input-prompt.d.ts +97 -0
  121. package/dist/ui/input-prompt.js +800 -0
  122. package/dist/ui/iteration-limit-overlay.d.ts +21 -0
  123. package/dist/ui/iteration-limit-overlay.js +150 -0
  124. package/dist/ui/keys-overlay.d.ts +14 -0
  125. package/dist/ui/keys-overlay.js +181 -0
  126. package/dist/ui/model-warning-overlay.d.ts +30 -0
  127. package/dist/ui/model-warning-overlay.js +171 -0
  128. package/dist/ui/overlay-controller.d.ts +25 -0
  129. package/dist/ui/overlay-controller.js +35 -0
  130. package/dist/ui/overlays.d.ts +47 -0
  131. package/dist/ui/overlays.js +627 -0
  132. package/dist/ui/permission-overlay.d.ts +16 -0
  133. package/dist/ui/permission-overlay.js +494 -0
  134. package/dist/ui/terminal.d.ts +117 -0
  135. package/dist/ui/terminal.js +237 -0
  136. package/dist/ui/todo-zone.d.ts +112 -0
  137. package/dist/ui/todo-zone.js +353 -0
  138. package/dist/ui/tools-overlay.d.ts +26 -0
  139. package/dist/ui/tools-overlay.js +278 -0
  140. package/dist/ui/tutorial-overlay.d.ts +10 -0
  141. package/dist/ui/tutorial-overlay.js +936 -0
  142. package/dist/ui/types.d.ts +103 -0
  143. package/dist/ui/types.js +33 -0
  144. package/dist/utils/credentials.d.ts +55 -0
  145. package/dist/utils/credentials.js +268 -0
  146. package/dist/utils/model-tiers.d.ts +37 -0
  147. package/dist/utils/model-tiers.js +118 -0
  148. package/dist/utils/project-memory.d.ts +47 -0
  149. package/dist/utils/project-memory.js +117 -0
  150. package/dist/utils/project-status.d.ts +56 -0
  151. package/dist/utils/project-status.js +237 -0
  152. package/package.json +66 -0
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Conversation Renderer
3
+ *
4
+ * Handles the scrolling zone - prints conversation messages.
5
+ * Uses console.log() which naturally scrolls content up.
6
+ */
7
+ import type { ToolMessage } from './types.js';
8
+ /**
9
+ * Render markdown text for terminal display
10
+ */
11
+ export declare function renderMarkdown(text: string): string;
12
+ /**
13
+ * Render inline markdown for streaming output
14
+ * Handles bold, italic, inline code, and fenced code blocks with syntax highlighting
15
+ * Used during streaming when we can't wait for full markdown parsing
16
+ */
17
+ export declare function renderInlineMarkdown(text: string): string;
18
+ /**
19
+ * Format tool result for display - handles strings, objects, arrays
20
+ * When verbose mode is enabled, shows more detailed output
21
+ */
22
+ export declare function formatToolResult(result: unknown): string;
23
+ /**
24
+ * Print user message to conversation
25
+ * Format: primary "❯ " + message content
26
+ */
27
+ export declare function printUserMessage(content: string): void;
28
+ /**
29
+ * Print assistant message to conversation
30
+ * Renders markdown (code blocks, bold, etc.)
31
+ */
32
+ export declare function printAssistantMessage(content: string): void;
33
+ /**
34
+ * Print assistant response (event-based rendering)
35
+ * Used when text is accumulated between tool calls.
36
+ * Format: "● " prefix + rendered markdown with syntax highlighting
37
+ */
38
+ export declare function printAssistantResponse(content: string): void;
39
+ /**
40
+ * Print tool execution log
41
+ * Format: warning "● toolName" + muted "(args)" + newline + muted " ⎿ " + result
42
+ * When verbose mode is enabled, shows full args
43
+ */
44
+ export declare function printToolLog(tool: ToolMessage): void;
45
+ /**
46
+ * Print tool execution inline (without creating ToolMessage)
47
+ * When verbose mode is enabled, shows full args instead of truncated
48
+ */
49
+ export declare function printToolExecution(name: string, args?: string, result?: string): void;
50
+ /**
51
+ * Print error message
52
+ * Format: error "✖ " + message
53
+ */
54
+ export declare function printError(message: string): void;
55
+ /**
56
+ * Print info message
57
+ * Format: muted text
58
+ */
59
+ export declare function printInfo(message: string): void;
60
+ /**
61
+ * Print success message
62
+ * Format: success "✔ " + message
63
+ */
64
+ export declare function printSuccess(message: string): void;
65
+ /**
66
+ * Print warning message
67
+ * Format: warning "⚠ " + message
68
+ */
69
+ export declare function printWarning(message: string): void;
70
+ /**
71
+ * Print just the ASCII logo (for startup before full welcome)
72
+ */
73
+ export declare function printLogo(version: string): void;
74
+ /**
75
+ * Print welcome banner with ASCII logo and contextual guidance
76
+ */
77
+ export declare function printWelcome(model: string, version: string): void;
78
+ /**
79
+ * Format a pending message for display in ephemeral zone
80
+ * (called by EphemeralZone, not printed directly)
81
+ */
82
+ export declare function formatPendingMessage(content: string): string;
@@ -0,0 +1,508 @@
1
+ /**
2
+ * Conversation Renderer
3
+ *
4
+ * Handles the scrolling zone - prints conversation messages.
5
+ * Uses console.log() which naturally scrolls content up.
6
+ */
7
+ import chalk, { Chalk } from 'chalk';
8
+ import { marked } from 'marked';
9
+ import { markedTerminal } from 'marked-terminal';
10
+ import { highlight } from 'cli-highlight';
11
+ import { getStyles } from '../themes/index.js';
12
+ import * as terminal from './terminal.js';
13
+ import { isVerboseEnabled } from '../settings/index.js';
14
+ import { detectProjectStatus, getWorkflowStage } from '../utils/project-status.js';
15
+ // Create chalk instance with colors forced on for our direct usage
16
+ // Note: FORCE_COLOR=3 is set in index.ts for global chalk-based libraries
17
+ const chalkWithColors = new Chalk({ level: 3 });
18
+ // Configure marked for terminal output
19
+ // Type assertion is needed because markedTerminal returns a type incompatible with marked's extension type
20
+ // but it works correctly at runtime. This is a known issue with the marked-terminal types.
21
+ marked.use(markedTerminal({
22
+ reflowText: true,
23
+ width: process.stdout.columns || 80,
24
+ // Enable styling options
25
+ showSectionPrefix: false,
26
+ tab: 2,
27
+ // Use our chalk instance with colors forced on
28
+ // marked-terminal uses these for inline formatting
29
+ strong: chalkWithColors.bold,
30
+ em: chalkWithColors.italic,
31
+ codespan: chalkWithColors.cyan,
32
+ }));
33
+ // =============================================================================
34
+ // Markdown Rendering
35
+ // =============================================================================
36
+ /**
37
+ * Render markdown text for terminal display
38
+ */
39
+ export function renderMarkdown(text) {
40
+ try {
41
+ // Preprocess: normalize list markers and indentation
42
+ const lines = text.split('\n');
43
+ const processedLines = lines.map((line) => {
44
+ // Normalize bullet lists: "* " or "- " -> "- "
45
+ // This prevents the asterisk from being confused with italic
46
+ if (/^\*\s{2,}/.test(line)) {
47
+ return line.replace(/^\*\s{2,}/, '- ');
48
+ }
49
+ // Remove leading 4 spaces that would make it a code block
50
+ // But preserve intentional code blocks (``` fenced)
51
+ if (line.startsWith(' ') && !line.startsWith(' ```')) {
52
+ return line.slice(4);
53
+ }
54
+ return line;
55
+ });
56
+ const processed = processedLines.join('\n');
57
+ // marked.parse returns string synchronously with these options
58
+ let rendered = marked.parse(processed, { async: false });
59
+ // Post-process: marked-terminal doesn't always render bold/italic inside list items
60
+ // Convert remaining **bold** and *italic* to ANSI codes
61
+ // Use chalkWithColors to ensure ANSI codes are always generated
62
+ const s = getStyles();
63
+ // Handle **`code`** (bold code) - common pattern
64
+ rendered = rendered.replace(/\*\*`([^`]+)`\*\*/g, (_match, code) => chalkWithColors.bold(s.primary(code)));
65
+ // Handle `code` inside **bold** - e.g., **some `code` here**
66
+ rendered = rendered.replace(/\*\*([^*]*)`([^`]+)`([^*]*)\*\*/g, (_match, before, code, after) => chalkWithColors.bold(`${before}${s.primary(code)}${after}`));
67
+ // Handle **bold** (not already processed)
68
+ rendered = rendered.replace(/\*\*([^*]+)\*\*/g, (_match, boldText) => chalkWithColors.bold(boldText));
69
+ // Handle *italic* - must not be at start of line (bullet) and not part of **
70
+ // Match *text* but not * (bullet with spaces) and not **
71
+ rendered = rendered.replace(/(?<![*\n])\*([^*\s][^*]*[^*\s])\*(?!\*)/g, (_match, italicText) => chalkWithColors.italic(italicText));
72
+ // Also handle single-word italic: *word*
73
+ rendered = rendered.replace(/(?<![*\n])\*(\S+)\*(?!\*)/g, (_match, word) => chalkWithColors.italic(word));
74
+ // Handle `code` (inline code) - use theme primary color
75
+ rendered = rendered.replace(/`([^`]+)`/g, (_match, code) => s.primary(code));
76
+ // Handle numbered lists that weren't processed (e.g., "1. text")
77
+ rendered = rendered.replace(/^(\d+)\.\s{2,}/gm, (_match, num) => chalkWithColors.bold(`${num}. `));
78
+ // Remove trailing newlines that marked adds
79
+ return rendered.replace(/\n+$/, '');
80
+ }
81
+ catch {
82
+ return text; // Fallback to raw text
83
+ }
84
+ }
85
+ /**
86
+ * Render inline markdown for streaming output
87
+ * Handles bold, italic, inline code, and fenced code blocks with syntax highlighting
88
+ * Used during streaming when we can't wait for full markdown parsing
89
+ */
90
+ export function renderInlineMarkdown(text) {
91
+ const s = getStyles();
92
+ // Handle fenced code blocks with syntax highlighting
93
+ // Match ```language\ncode\n``` pattern
94
+ let result = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
95
+ try {
96
+ // Apply syntax highlighting
97
+ const highlighted = highlight(code.trimEnd(), {
98
+ language: lang || undefined,
99
+ ignoreIllegals: true,
100
+ });
101
+ return '\n' + highlighted + '\n';
102
+ }
103
+ catch {
104
+ // Fallback to muted code display
105
+ return '\n' + s.muted(code.trimEnd()) + '\n';
106
+ }
107
+ });
108
+ // Handle **`code`** (bold code) - common pattern
109
+ result = result.replace(/\*\*`([^`]+)`\*\*/g, (_match, code) => chalkWithColors.bold(s.primary(code)));
110
+ // Handle `code` inside **bold** - e.g., **some `code` here**
111
+ result = result.replace(/\*\*([^*]*)`([^`]+)`([^*]*)\*\*/g, (_match, before, code, after) => chalkWithColors.bold(`${before}${s.primary(code)}${after}`));
112
+ // Handle **bold**
113
+ result = result.replace(/\*\*([^*]+)\*\*/g, (_match, boldText) => chalkWithColors.bold(boldText));
114
+ // Handle *italic* - careful not to match bullet points (* ) or **
115
+ result = result.replace(/(?<![*\s])\*([^*\s][^*]*[^*\s])\*(?!\*)/g, (_match, italicText) => chalkWithColors.italic(italicText));
116
+ // Single-word italic
117
+ result = result.replace(/(?<![*\s])\*(\S+)\*(?!\*)/g, (_match, word) => chalkWithColors.italic(word));
118
+ // Handle `code` - use theme primary color
119
+ result = result.replace(/`([^`]+)`/g, (_match, code) => s.primary(code));
120
+ return result;
121
+ }
122
+ // =============================================================================
123
+ // Tool Result Formatting
124
+ // =============================================================================
125
+ /**
126
+ * Format tool result for display - handles strings, objects, arrays
127
+ * When verbose mode is enabled, shows more detailed output
128
+ */
129
+ export function formatToolResult(result) {
130
+ const s = getStyles();
131
+ const verbose = isVerboseEnabled();
132
+ if (result === null || result === undefined) {
133
+ return '';
134
+ }
135
+ // String result - count lines or truncate
136
+ if (typeof result === 'string') {
137
+ const allLines = result.split('\n');
138
+ const nonEmptyLines = allLines.filter((l) => l.trim()).length;
139
+ if (verbose) {
140
+ // Verbose mode: show first few lines as preview, then count
141
+ const previewLines = 5;
142
+ if (allLines.length > previewLines) {
143
+ const preview = allLines.slice(0, previewLines).join('\n');
144
+ return s.muted(`${preview}\n ... (${String(nonEmptyLines)} lines total)`);
145
+ }
146
+ // Short enough to show fully
147
+ return s.muted(result);
148
+ }
149
+ else {
150
+ // Normal mode: just show line count or truncated single line
151
+ if (nonEmptyLines > 3) {
152
+ return s.muted(`${String(nonEmptyLines)} lines`);
153
+ }
154
+ const truncated = result.slice(0, 80).replace(/\n/g, ' ');
155
+ return s.muted(truncated + (result.length > 80 ? '...' : ''));
156
+ }
157
+ }
158
+ // Array result - show count and type hint
159
+ if (Array.isArray(result)) {
160
+ const len = result.length;
161
+ if (len === 0)
162
+ return s.muted('empty array');
163
+ // Try to describe contents
164
+ const first = result[0];
165
+ if (typeof first === 'string') {
166
+ return s.muted(`${String(len)} items`);
167
+ }
168
+ else if (typeof first === 'object' && first !== null) {
169
+ // Check for common patterns like todos, files, branches
170
+ if ('content' in first && 'status' in first) {
171
+ return s.muted(`${String(len)} todo items`);
172
+ }
173
+ if ('name' in first || 'path' in first) {
174
+ return s.muted(`${String(len)} items`);
175
+ }
176
+ }
177
+ return s.muted(`${String(len)} items`);
178
+ }
179
+ // Object result - try to extract meaningful summary
180
+ if (typeof result === 'object') {
181
+ const obj = result;
182
+ // Git status pattern
183
+ if ('branch' in obj && 'clean' in obj) {
184
+ const branch = obj.branch;
185
+ const clean = obj.clean;
186
+ return s.muted(`${branch}${clean ? ' (clean)' : ' (changes)'}`);
187
+ }
188
+ // Git diff pattern
189
+ if ('files' in obj && Array.isArray(obj.files)) {
190
+ const files = obj.files;
191
+ if (files.length === 0)
192
+ return s.muted('no changes');
193
+ if (verbose) {
194
+ // Show file names in verbose mode
195
+ const fileNames = files.map((f) => {
196
+ if (typeof f === 'string')
197
+ return f;
198
+ if (typeof f === 'object' && f !== null && 'path' in f)
199
+ return f.path;
200
+ if (typeof f === 'object' && f !== null && 'file' in f)
201
+ return f.file;
202
+ return String(f);
203
+ });
204
+ const preview = fileNames.slice(0, 10).join('\n ');
205
+ if (fileNames.length > 10) {
206
+ return s.muted(`${preview}\n ... (${String(fileNames.length)} files total)`);
207
+ }
208
+ return s.muted(preview);
209
+ }
210
+ return s.muted(`${String(files.length)} file(s) changed`);
211
+ }
212
+ // Git branch pattern
213
+ if ('current' in obj && 'branches' in obj) {
214
+ const branches = obj.branches;
215
+ return s.muted(`${String(branches?.length ?? 0)} branches`);
216
+ }
217
+ // File read pattern
218
+ if ('content' in obj) {
219
+ const content = String(obj.content);
220
+ const allLines = content.split('\n');
221
+ const lineCount = allLines.length;
222
+ if (verbose) {
223
+ // Show preview in verbose mode
224
+ const previewLines = 5;
225
+ if (allLines.length > previewLines) {
226
+ const preview = allLines.slice(0, previewLines).join('\n');
227
+ return s.muted(`${preview}\n ... (${String(lineCount)} lines total)`);
228
+ }
229
+ return s.muted(content);
230
+ }
231
+ return s.muted(`${String(lineCount)} lines`);
232
+ }
233
+ // Bash/command pattern
234
+ if ('stdout' in obj || 'output' in obj) {
235
+ const outValue = (obj.stdout ?? obj.output) ?? '';
236
+ const out = typeof outValue === 'string' ? outValue : JSON.stringify(outValue);
237
+ const allLines = out.split('\n');
238
+ const nonEmptyLines = allLines.filter((l) => l.trim()).length;
239
+ if (verbose && nonEmptyLines > 0) {
240
+ const previewLines = 5;
241
+ if (allLines.length > previewLines) {
242
+ const preview = allLines.slice(0, previewLines).join('\n');
243
+ return s.muted(`${preview}\n ... (${String(nonEmptyLines)} lines total)`);
244
+ }
245
+ return s.muted(out);
246
+ }
247
+ if (nonEmptyLines > 0)
248
+ return s.muted(`${String(nonEmptyLines)} lines`);
249
+ return s.muted('done');
250
+ }
251
+ // Success message pattern
252
+ if ('success' in obj && typeof obj.message === 'string') {
253
+ return s.muted(obj.message.slice(0, 60));
254
+ }
255
+ // Fallback - show key count or full object in verbose mode
256
+ const keys = Object.keys(obj);
257
+ if (verbose) {
258
+ // Verbose mode: show full object (limited depth)
259
+ try {
260
+ const json = JSON.stringify(obj, null, 2);
261
+ const lines = json.split('\n');
262
+ if (lines.length > 15) {
263
+ return s.muted(lines.slice(0, 15).join('\n') + '\n ... (truncated)');
264
+ }
265
+ return s.muted(json);
266
+ }
267
+ catch {
268
+ return s.muted(`${String(keys.length)} fields`);
269
+ }
270
+ }
271
+ if (keys.length <= 3) {
272
+ return s.muted(keys.join(', '));
273
+ }
274
+ return s.muted(`${String(keys.length)} fields`);
275
+ }
276
+ // Primitive - convert to string safely
277
+ const resultStr = typeof result === 'string' ? result :
278
+ typeof result === 'number' ? String(result) :
279
+ typeof result === 'boolean' ? String(result) :
280
+ JSON.stringify(result);
281
+ return s.muted(resultStr.slice(0, 60));
282
+ }
283
+ // =============================================================================
284
+ // Message Printing
285
+ // =============================================================================
286
+ /**
287
+ * Print user message to conversation
288
+ * Format: primary "❯ " + message content
289
+ */
290
+ export function printUserMessage(content) {
291
+ const s = getStyles();
292
+ console.log(s.primary('❯ ') + content);
293
+ console.log(''); // Blank line after
294
+ }
295
+ /**
296
+ * Print assistant message to conversation
297
+ * Renders markdown (code blocks, bold, etc.)
298
+ */
299
+ export function printAssistantMessage(content) {
300
+ if (!content.trim())
301
+ return;
302
+ const s = getStyles();
303
+ console.log(s.success('Agent: '));
304
+ console.log(renderMarkdown(content));
305
+ console.log(''); // Blank line after
306
+ }
307
+ /**
308
+ * Print assistant response (event-based rendering)
309
+ * Used when text is accumulated between tool calls.
310
+ * Format: "● " prefix + rendered markdown with syntax highlighting
311
+ */
312
+ export function printAssistantResponse(content) {
313
+ if (!content.trim())
314
+ return;
315
+ const s = getStyles();
316
+ // Use bullet prefix (consistent with tool output style)
317
+ // Apply renderInlineMarkdown which handles code blocks with syntax highlighting
318
+ const rendered = renderInlineMarkdown(content);
319
+ console.log(s.primary('● ') + rendered);
320
+ }
321
+ /**
322
+ * Print tool execution log
323
+ * Format: warning "● toolName" + muted "(args)" + newline + muted " ⎿ " + result
324
+ * When verbose mode is enabled, shows full args
325
+ */
326
+ export function printToolLog(tool) {
327
+ const s = getStyles();
328
+ const verbose = isVerboseEnabled();
329
+ // In verbose mode, show full args; otherwise truncate
330
+ let displayArgs = tool.args;
331
+ if (tool.args && !verbose && tool.args.length > 60) {
332
+ displayArgs = tool.args.slice(0, 57) + '...';
333
+ }
334
+ const toolHeader = displayArgs
335
+ ? s.warning(`● ${tool.name}`) + s.secondary(`(${displayArgs})`)
336
+ : s.warning(`● ${tool.name}`);
337
+ console.log(toolHeader);
338
+ if (tool.result) {
339
+ console.log(s.muted(' ⎿ ') + tool.result);
340
+ }
341
+ console.log(''); // Blank line after
342
+ }
343
+ /**
344
+ * Print tool execution inline (without creating ToolMessage)
345
+ * When verbose mode is enabled, shows full args instead of truncated
346
+ */
347
+ export function printToolExecution(name, args, result) {
348
+ const s = getStyles();
349
+ const verbose = isVerboseEnabled();
350
+ // In verbose mode, show full args; otherwise truncate
351
+ let displayArgs = args;
352
+ if (args && !verbose && args.length > 60) {
353
+ displayArgs = args.slice(0, 57) + '...';
354
+ }
355
+ const toolHeader = displayArgs
356
+ ? s.warning(`● ${name}`) + s.secondary(`(${displayArgs})`)
357
+ : s.warning(`● ${name}`);
358
+ console.log(toolHeader);
359
+ if (result) {
360
+ console.log(s.muted(' ⎿ ') + result);
361
+ }
362
+ console.log(''); // Blank line after
363
+ }
364
+ /**
365
+ * Print error message
366
+ * Format: error "✖ " + message
367
+ */
368
+ export function printError(message) {
369
+ const s = getStyles();
370
+ console.log(s.error('✖ ') + message);
371
+ console.log('');
372
+ }
373
+ /**
374
+ * Print info message
375
+ * Format: muted text
376
+ */
377
+ export function printInfo(message) {
378
+ const s = getStyles();
379
+ console.log(s.muted(message));
380
+ }
381
+ /**
382
+ * Print success message
383
+ * Format: success "✔ " + message
384
+ */
385
+ export function printSuccess(message) {
386
+ const s = getStyles();
387
+ console.log(s.success('✔ ') + message);
388
+ }
389
+ /**
390
+ * Print warning message
391
+ * Format: warning "⚠ " + message
392
+ */
393
+ export function printWarning(message) {
394
+ const s = getStyles();
395
+ console.log(s.warning('⚠ ') + message);
396
+ }
397
+ // =============================================================================
398
+ // Welcome Banner
399
+ // =============================================================================
400
+ /**
401
+ * Print just the ASCII logo (for startup before full welcome)
402
+ */
403
+ export function printLogo(version) {
404
+ const s = getStyles();
405
+ terminal.clearScreen();
406
+ console.log('');
407
+ console.log(s.primary(" ___ ___ _ __ ___ _ __ (_) |_ __"));
408
+ console.log(s.primary(" / __|/ _ \\| '_ ` _ \\| '_ \\| | | '__|"));
409
+ console.log(s.primary(" | (__| (_) | | | | | | |_) | | | |"));
410
+ console.log(s.primary(" \\___|\\___/|_| |_| |_| .__/|_|_|_|"));
411
+ console.log(s.primary(' | |') + s.secondary(' .dev'));
412
+ console.log(s.primary(' |_|') + s.muted(` v${version}`));
413
+ console.log('');
414
+ console.log(chalk.bold('@compilr-dev/cli') + s.muted(' - AI-powered coding assistant'));
415
+ console.log('');
416
+ }
417
+ /**
418
+ * Print welcome banner with ASCII logo and contextual guidance
419
+ */
420
+ export function printWelcome(model, version) {
421
+ const s = getStyles();
422
+ console.log('');
423
+ console.log(s.primary(" ___ ___ _ __ ___ _ __ (_) |_ __"));
424
+ console.log(s.primary(" / __|/ _ \\| '_ ` _ \\| '_ \\| | | '__|"));
425
+ console.log(s.primary(" | (__| (_) | | | | | | |_) | | | |"));
426
+ console.log(s.primary(" \\___|\\___/|_| |_| |_| .__/|_|_|_|"));
427
+ console.log(s.primary(' | |') + s.secondary(' .dev'));
428
+ console.log(s.primary(' |_|') + s.muted(` v${version}`));
429
+ console.log('');
430
+ console.log(chalk.bold('@compilr-dev/cli') + s.muted(' - AI-powered coding assistant'));
431
+ console.log('');
432
+ console.log(s.muted(`Model: ${model}`));
433
+ console.log('');
434
+ // Detect project status and show contextual guidance
435
+ const status = detectProjectStatus();
436
+ const stage = getWorkflowStage(status);
437
+ printWorkflowGuidance(status, stage);
438
+ console.log(s.muted('Type /help for commands, /exit to quit'));
439
+ console.log('');
440
+ }
441
+ /**
442
+ * Print workflow guidance based on project status
443
+ */
444
+ function printWorkflowGuidance(status, stage) {
445
+ const s = getStyles();
446
+ switch (stage) {
447
+ case 'new':
448
+ // Brand new directory, nothing exists
449
+ console.log(s.muted('Get started:'));
450
+ console.log(s.muted(' /init ') + s.secondary('Create project structure'));
451
+ console.log(s.muted(' /sketch ') + s.secondary('Quick 6-question outline'));
452
+ console.log(s.muted(' /tutorial ') + s.secondary('Learn the workflow'));
453
+ console.log('');
454
+ break;
455
+ case 'initialized':
456
+ // Existing project, but no compilr setup
457
+ if (status.isExistingProject && !status.hasMemory) {
458
+ console.log(s.muted(`Detected ${status.projectType || 'existing'} project. Define requirements:`));
459
+ console.log(s.muted(' /design ') + s.secondary('Agent-driven requirements'));
460
+ console.log(s.muted(' /sketch ') + s.secondary('Quick project outline'));
461
+ console.log(s.muted(' /tutorial ') + s.secondary('Learn the workflow'));
462
+ console.log('');
463
+ }
464
+ break;
465
+ case 'designing':
466
+ // Has PRD or initialized, but no backlog yet
467
+ console.log(s.muted('Project initialized. Create your backlog:'));
468
+ console.log(s.muted(' /design ') + s.secondary('Refine requirements'));
469
+ console.log(s.muted(' /backlog ') + s.secondary('View/manage tasks'));
470
+ console.log('');
471
+ break;
472
+ case 'building':
473
+ // Has backlog with pending items
474
+ if (status.backlogStats) {
475
+ const { pending, inProgress } = status.backlogStats;
476
+ const taskInfo = inProgress > 0
477
+ ? `${String(inProgress)} in progress, ${String(pending)} pending`
478
+ : `${String(pending)} tasks pending`;
479
+ console.log(s.muted(`Backlog: ${taskInfo}`));
480
+ console.log(s.muted(' /backlog ') + s.secondary('View tasks'));
481
+ console.log(s.muted(' "build next item" ') + s.secondary('Start working'));
482
+ console.log('');
483
+ }
484
+ break;
485
+ case 'maintaining':
486
+ // Most items completed
487
+ if (status.backlogStats) {
488
+ const { total, completed } = status.backlogStats;
489
+ console.log(s.muted(`Backlog: ${String(completed)}/${String(total)} completed`));
490
+ console.log(s.muted(' /backlog ') + s.secondary('View tasks'));
491
+ console.log(s.muted(' /refine ') + s.secondary('Add new requirements'));
492
+ console.log('');
493
+ }
494
+ break;
495
+ }
496
+ }
497
+ // =============================================================================
498
+ // Pending Message Display
499
+ // =============================================================================
500
+ /**
501
+ * Format a pending message for display in ephemeral zone
502
+ * (called by EphemeralZone, not printed directly)
503
+ */
504
+ export function formatPendingMessage(content) {
505
+ const s = getStyles();
506
+ const truncated = content.length > 60 ? content.slice(0, 57) + '...' : content;
507
+ return s.muted('⏳ ') + s.muted(truncated);
508
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Diff Formatting Utility
3
+ *
4
+ * Generates colorized diff output for file edits, similar to Claude Code's display.
5
+ * Shows removed lines with red background and added lines with green background.
6
+ */
7
+ export interface DiffResult {
8
+ /** Formatted lines to display */
9
+ lines: string[];
10
+ /** Number of additions */
11
+ additions: number;
12
+ /** Number of removals */
13
+ removals: number;
14
+ }
15
+ /**
16
+ * Generate a formatted diff for an edit operation.
17
+ * Supports both single replacement and replaceAll.
18
+ * Shows full lines being modified with proper addition/removal counts.
19
+ *
20
+ * @param filePath - Path to the file being edited
21
+ * @param oldText - Text being replaced
22
+ * @param newText - Replacement text
23
+ * @param replaceAll - Whether all occurrences are being replaced
24
+ * @returns Formatted diff result, or null if file can't be read
25
+ */
26
+ export declare function generateEditDiff(filePath: string, oldText: string, newText: string, replaceAll?: boolean): DiffResult | null;
27
+ /**
28
+ * Format a diff header showing what changed
29
+ */
30
+ export declare function formatDiffHeader(filePath: string, additions: number, removals: number): string;
31
+ /**
32
+ * Generate a simple diff for write_file operations (new content only)
33
+ */
34
+ export declare function generateWriteDiff(filePath: string, content: string, _isNew: boolean): DiffResult;
35
+ /**
36
+ * Format header for write_file operations
37
+ */
38
+ export declare function formatWriteHeader(filePath: string, lineCount: number, isNew: boolean): string;