@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,75 @@
1
+ /**
2
+ * REPL State
3
+ *
4
+ * Central state management for the REPL.
5
+ * Components read from this state but don't own it.
6
+ */
7
+ import type { ConversationMessage, TodoItem, SpinnerState, REPLState } from './ui/types.js';
8
+ /**
9
+ * Create initial REPL state
10
+ */
11
+ export declare function createInitialState(model: string): REPLState;
12
+ /**
13
+ * Update todos
14
+ */
15
+ export declare function updateTodos(state: REPLState, todos: TodoItem[]): void;
16
+ /**
17
+ * Add a pending message (queued while agent works)
18
+ */
19
+ export declare function addPendingMessage(state: REPLState, message: string): void;
20
+ /**
21
+ * Get and clear pending messages
22
+ */
23
+ export declare function clearPendingMessages(state: REPLState): string[];
24
+ /**
25
+ * Add message to conversation history
26
+ */
27
+ export declare function addToHistory(state: REPLState, message: ConversationMessage): void;
28
+ /**
29
+ * Clear conversation history
30
+ */
31
+ export declare function clearHistory(state: REPLState): void;
32
+ /**
33
+ * Set spinner state
34
+ */
35
+ export declare function setSpinner(state: REPLState, spinner: SpinnerState | null): void;
36
+ /**
37
+ * Set agent running state
38
+ */
39
+ export declare function setAgentRunning(state: REPLState, running: boolean): void;
40
+ /**
41
+ * Add to input history
42
+ */
43
+ export declare function addToInputHistory(state: REPLState, input: string): void;
44
+ /**
45
+ * Add tool to always-allowed set
46
+ */
47
+ export declare function allowTool(state: REPLState, toolName: string): void;
48
+ /**
49
+ * Check if tool is always allowed
50
+ */
51
+ export declare function isToolAllowed(state: REPLState, toolName: string): boolean;
52
+ /**
53
+ * Reset allowed tools
54
+ */
55
+ export declare function resetAllowedTools(state: REPLState): void;
56
+ /**
57
+ * Get active todo (in_progress)
58
+ */
59
+ export declare function getActiveTodo(state: REPLState): TodoItem | undefined;
60
+ /**
61
+ * Get completed todos count
62
+ */
63
+ export declare function getCompletedCount(state: REPLState): number;
64
+ /**
65
+ * Get pending todos count
66
+ */
67
+ export declare function getPendingCount(state: REPLState): number;
68
+ /**
69
+ * Get total message count
70
+ */
71
+ export declare function getMessageCount(state: REPLState): number;
72
+ /**
73
+ * Get turn count (user message + assistant response pairs)
74
+ */
75
+ export declare function getTurnCount(state: REPLState): number;
package/dist/state.js ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * REPL State
3
+ *
4
+ * Central state management for the REPL.
5
+ * Components read from this state but don't own it.
6
+ */
7
+ // =============================================================================
8
+ // State Creation
9
+ // =============================================================================
10
+ /**
11
+ * Create initial REPL state
12
+ */
13
+ export function createInitialState(model) {
14
+ return {
15
+ conversationHistory: [],
16
+ spinner: null,
17
+ todos: [],
18
+ pendingMessages: [],
19
+ isAgentRunning: false,
20
+ currentModel: model,
21
+ inputHistory: [],
22
+ allowedTools: new Set(),
23
+ };
24
+ }
25
+ // =============================================================================
26
+ // State Update Helpers
27
+ // =============================================================================
28
+ /**
29
+ * Update todos
30
+ */
31
+ export function updateTodos(state, todos) {
32
+ state.todos = todos;
33
+ }
34
+ /**
35
+ * Add a pending message (queued while agent works)
36
+ */
37
+ export function addPendingMessage(state, message) {
38
+ state.pendingMessages.push(message);
39
+ }
40
+ /**
41
+ * Get and clear pending messages
42
+ */
43
+ export function clearPendingMessages(state) {
44
+ const messages = [...state.pendingMessages];
45
+ state.pendingMessages = [];
46
+ return messages;
47
+ }
48
+ /**
49
+ * Add message to conversation history
50
+ */
51
+ export function addToHistory(state, message) {
52
+ state.conversationHistory.push(message);
53
+ }
54
+ /**
55
+ * Clear conversation history
56
+ */
57
+ export function clearHistory(state) {
58
+ state.conversationHistory = [];
59
+ }
60
+ /**
61
+ * Set spinner state
62
+ */
63
+ export function setSpinner(state, spinner) {
64
+ state.spinner = spinner;
65
+ }
66
+ /**
67
+ * Set agent running state
68
+ */
69
+ export function setAgentRunning(state, running) {
70
+ state.isAgentRunning = running;
71
+ }
72
+ /**
73
+ * Add to input history
74
+ */
75
+ export function addToInputHistory(state, input) {
76
+ if (input.trim() && (state.inputHistory.length === 0 || state.inputHistory[state.inputHistory.length - 1] !== input)) {
77
+ state.inputHistory.push(input);
78
+ }
79
+ }
80
+ /**
81
+ * Add tool to always-allowed set
82
+ */
83
+ export function allowTool(state, toolName) {
84
+ state.allowedTools.add(toolName);
85
+ }
86
+ /**
87
+ * Check if tool is always allowed
88
+ */
89
+ export function isToolAllowed(state, toolName) {
90
+ return state.allowedTools.has(toolName);
91
+ }
92
+ /**
93
+ * Reset allowed tools
94
+ */
95
+ export function resetAllowedTools(state) {
96
+ state.allowedTools.clear();
97
+ }
98
+ // =============================================================================
99
+ // Computed Values
100
+ // =============================================================================
101
+ /**
102
+ * Get active todo (in_progress)
103
+ */
104
+ export function getActiveTodo(state) {
105
+ return state.todos.find((t) => t.status === 'in_progress');
106
+ }
107
+ /**
108
+ * Get completed todos count
109
+ */
110
+ export function getCompletedCount(state) {
111
+ return state.todos.filter((t) => t.status === 'completed').length;
112
+ }
113
+ /**
114
+ * Get pending todos count
115
+ */
116
+ export function getPendingCount(state) {
117
+ return state.todos.filter((t) => t.status === 'pending').length;
118
+ }
119
+ /**
120
+ * Get total message count
121
+ */
122
+ export function getMessageCount(state) {
123
+ return state.conversationHistory.length;
124
+ }
125
+ /**
126
+ * Get turn count (user message + assistant response pairs)
127
+ */
128
+ export function getTurnCount(state) {
129
+ return Math.floor(state.conversationHistory.filter((m) => m.type === 'user').length);
130
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tabbed Help Menu
3
+ *
4
+ * Claude Code-style tabbed help menu with keyboard navigation.
5
+ * Uses alternate screen buffer to preserve terminal history.
6
+ */
7
+ /**
8
+ * Show the tabbed help menu (in-place, not full screen)
9
+ * Returns a promise that resolves when the menu is closed
10
+ */
11
+ export declare function showHelpMenu(): Promise<void>;
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Tabbed Help Menu
3
+ *
4
+ * Claude Code-style tabbed help menu with keyboard navigation.
5
+ * Uses alternate screen buffer to preserve terminal history.
6
+ */
7
+ import pc from 'picocolors';
8
+ import { getCustomCommandRegistry } from './commands/index.js';
9
+ // ANSI escape codes
10
+ const ANSI = {
11
+ HIDE_CURSOR: '\x1B[?25l',
12
+ SHOW_CURSOR: '\x1B[?25h',
13
+ CLEAR_LINE: '\x1B[2K',
14
+ CLEAR_TO_END: '\x1B[J',
15
+ SAVE_CURSOR: '\x1B[s',
16
+ RESTORE_CURSOR: '\x1B[u',
17
+ MOVE_UP: (n) => `\x1B[${String(n)}A`,
18
+ };
19
+ // Version display
20
+ const VERSION = 'compilr.dev/agent';
21
+ const TABS = [
22
+ { id: 'general', label: 'general' },
23
+ { id: 'commands', label: 'commands' },
24
+ { id: 'custom-commands', label: 'custom-commands' },
25
+ ];
26
+ const COMMANDS = [
27
+ { name: '/help', description: 'Show this help menu' },
28
+ { name: '/exit', description: 'Exit the REPL' },
29
+ { name: '/clear', description: 'Clear conversation history' },
30
+ { name: '/compact', description: 'Summarize old messages to free context' },
31
+ { name: '/tools', description: 'List available tools' },
32
+ { name: '/tokens', description: 'Show session token usage' },
33
+ { name: '/context', description: 'Visualize current context usage' },
34
+ { name: '/model', description: 'Show or change the current model' },
35
+ { name: '/provider', description: 'Show or change the LLM provider' },
36
+ { name: '/history', description: 'Show conversation history' },
37
+ { name: '/save', description: 'Save conversation to file' },
38
+ { name: '/load', description: 'Load conversation from file' },
39
+ { name: '/reset', description: 'Reset agent state' },
40
+ { name: '/verbose', description: 'Toggle verbose output mode' },
41
+ { name: '/debug', description: 'Toggle debug mode' },
42
+ ];
43
+ const VISIBLE_COMMANDS = 10;
44
+ /**
45
+ * Get terminal dimensions
46
+ */
47
+ function getTerminalSize() {
48
+ return {
49
+ rows: process.stdout.rows || 24,
50
+ cols: process.stdout.columns || 80,
51
+ };
52
+ }
53
+ /**
54
+ * Render the header with version and tabs
55
+ */
56
+ function renderHeader(state) {
57
+ const lines = [];
58
+ const { cols } = getTerminalSize();
59
+ // Separator line (cols - 1 to avoid terminal wrapping issues)
60
+ lines.push(pc.dim('─'.repeat(Math.max(1, cols - 1))));
61
+ // Version + tabs line
62
+ let tabLine = ' ' + pc.bold(pc.cyan(VERSION)) + ' ';
63
+ for (let i = 0; i < TABS.length; i++) {
64
+ const tab = TABS[i];
65
+ if (i === state.currentTab) {
66
+ tabLine += pc.bgBlue(pc.white(` ${tab.label} `)) + ' ';
67
+ }
68
+ else {
69
+ tabLine += pc.dim(` ${tab.label} `) + ' ';
70
+ }
71
+ }
72
+ tabLine += pc.dim('(tab to cycle)');
73
+ lines.push(tabLine);
74
+ lines.push('');
75
+ return lines;
76
+ }
77
+ /**
78
+ * Render the General tab content
79
+ */
80
+ function renderGeneralTab() {
81
+ const lines = [];
82
+ lines.push(' An AI assistant that understands your codebase, makes edits with your permission, and executes commands — right from your terminal.');
83
+ lines.push('');
84
+ lines.push(pc.bold(' Shortcuts'));
85
+ const shortcuts = [
86
+ ['/ for commands', 'Enter to submit'],
87
+ ['↑↓ for history', 'Esc to cancel'],
88
+ ['Tab to autocomplete', 'Ctrl+C to exit'],
89
+ ];
90
+ for (const [left, right] of shortcuts) {
91
+ lines.push(' ' + pc.cyan(left.padEnd(25)) + pc.cyan(right));
92
+ }
93
+ lines.push('');
94
+ lines.push(pc.bold(' Features'));
95
+ lines.push(' ' + pc.dim('• Multi-LLM support (Claude, OpenAI, Gemini, Ollama)'));
96
+ lines.push(' ' + pc.dim('• Tool execution with permission prompts'));
97
+ lines.push(' ' + pc.dim('• Context management and compaction'));
98
+ lines.push(' ' + pc.dim('• Slash command autocomplete'));
99
+ return lines;
100
+ }
101
+ /**
102
+ * Render the Commands tab content
103
+ */
104
+ function renderCommandsTab(state) {
105
+ const lines = [];
106
+ lines.push(pc.bold(' Browse available commands:'));
107
+ lines.push('');
108
+ const visibleCommands = COMMANDS.slice(state.scrollOffset, state.scrollOffset + VISIBLE_COMMANDS);
109
+ if (state.scrollOffset > 0) {
110
+ lines.push(pc.dim(' ↑'));
111
+ }
112
+ else {
113
+ lines.push('');
114
+ }
115
+ for (let i = 0; i < visibleCommands.length; i++) {
116
+ const cmd = visibleCommands[i];
117
+ const globalIndex = state.scrollOffset + i;
118
+ const isSelected = globalIndex === state.selectedCommand;
119
+ const prefix = isSelected ? pc.cyan(' ❯ ') : ' ';
120
+ const name = isSelected
121
+ ? pc.cyan(cmd.name.padEnd(20))
122
+ : pc.dim(cmd.name.padEnd(20));
123
+ const desc = pc.dim(cmd.description);
124
+ lines.push(prefix + name + desc);
125
+ }
126
+ if (state.scrollOffset + VISIBLE_COMMANDS < COMMANDS.length) {
127
+ lines.push(pc.dim(' ↓'));
128
+ }
129
+ else {
130
+ lines.push('');
131
+ }
132
+ return lines;
133
+ }
134
+ /**
135
+ * Render the Custom Commands tab content
136
+ */
137
+ function renderCustomCommandsTab() {
138
+ const lines = [];
139
+ const registry = getCustomCommandRegistry();
140
+ const commands = registry.getAll();
141
+ lines.push(pc.bold(' Custom commands:'));
142
+ lines.push('');
143
+ if (commands.length === 0) {
144
+ lines.push(pc.dim(' No custom commands found'));
145
+ lines.push('');
146
+ lines.push(pc.dim(' Create commands with /commands'));
147
+ lines.push(pc.dim(' or add .md files to:'));
148
+ lines.push(pc.dim(` ${registry.getProjectDir()}/`));
149
+ lines.push(pc.dim(` ${registry.getUserDir()}/`));
150
+ }
151
+ else {
152
+ for (const cmd of commands) {
153
+ const name = `/${cmd.name}`.padEnd(20);
154
+ const desc = cmd.description.slice(0, 40) + (cmd.description.length > 40 ? '...' : '');
155
+ const location = ` (${cmd.location})`;
156
+ lines.push(' ' + pc.cyan(name) + pc.dim(desc) + pc.dim(location));
157
+ }
158
+ lines.push('');
159
+ lines.push(pc.dim(' Use /commands to manage custom commands'));
160
+ }
161
+ return lines;
162
+ }
163
+ /**
164
+ * Render the footer
165
+ */
166
+ function renderFooter() {
167
+ const lines = [];
168
+ lines.push('');
169
+ lines.push(pc.dim(' For more help: https://github.com/scozzola/compilr-dev-cli'));
170
+ lines.push('');
171
+ lines.push(pc.dim(' Esc to exit'));
172
+ return lines;
173
+ }
174
+ /**
175
+ * Build all lines for current state
176
+ */
177
+ function buildLines(state) {
178
+ const allLines = [];
179
+ allLines.push(...renderHeader(state));
180
+ switch (TABS[state.currentTab].id) {
181
+ case 'general':
182
+ allLines.push(...renderGeneralTab());
183
+ break;
184
+ case 'commands':
185
+ allLines.push(...renderCommandsTab(state));
186
+ break;
187
+ case 'custom-commands':
188
+ allLines.push(...renderCustomCommandsTab());
189
+ break;
190
+ }
191
+ allLines.push(...renderFooter());
192
+ return allLines;
193
+ }
194
+ /**
195
+ * Render the menu in-place (not full screen)
196
+ * Returns the number of lines rendered
197
+ */
198
+ function render(state, prevLineCount) {
199
+ const lines = buildLines(state);
200
+ // Clear previous render
201
+ if (prevLineCount > 0) {
202
+ // Move to column 1, move up to first line, clear everything below
203
+ process.stdout.write('\r');
204
+ if (prevLineCount > 1) {
205
+ process.stdout.write(ANSI.MOVE_UP(prevLineCount - 1));
206
+ }
207
+ process.stdout.write(ANSI.CLEAR_TO_END);
208
+ }
209
+ // Render new content
210
+ process.stdout.write(lines.join('\n'));
211
+ return lines.length;
212
+ }
213
+ /**
214
+ * Handle keypress
215
+ * Returns { shouldContinue, lineCount } - lineCount is updated if re-render happened
216
+ */
217
+ function handleKey(data, state, lineCount) {
218
+ const isEscape = data.length === 1 && data[0] === 0x1b;
219
+ const isTab = data.length === 1 && data[0] === 0x09;
220
+ const isUpArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x41;
221
+ const isDownArrow = data.length === 3 && data[0] === 0x1b && data[1] === 0x5b && data[2] === 0x42;
222
+ const isCtrlC = data.length === 1 && data[0] === 0x03;
223
+ const isEnter = data.length === 1 && (data[0] === 0x0d || data[0] === 0x0a);
224
+ // Escape or Ctrl+C - exit
225
+ if (isEscape || isCtrlC) {
226
+ return { shouldContinue: false, lineCount };
227
+ }
228
+ // Enter on commands tab - could execute command (for now just exit)
229
+ if (isEnter && TABS[state.currentTab].id === 'commands') {
230
+ return { shouldContinue: false, lineCount };
231
+ }
232
+ // Tab - cycle tabs
233
+ if (isTab) {
234
+ state.currentTab = (state.currentTab + 1) % TABS.length;
235
+ if (TABS[state.currentTab].id === 'commands') {
236
+ state.selectedCommand = 0;
237
+ state.scrollOffset = 0;
238
+ }
239
+ const newLineCount = render(state, lineCount);
240
+ return { shouldContinue: true, lineCount: newLineCount };
241
+ }
242
+ // Arrow keys only work in commands tab
243
+ if (TABS[state.currentTab].id === 'commands') {
244
+ if (isUpArrow) {
245
+ if (state.selectedCommand > 0) {
246
+ state.selectedCommand--;
247
+ if (state.selectedCommand < state.scrollOffset) {
248
+ state.scrollOffset = state.selectedCommand;
249
+ }
250
+ const newLineCount = render(state, lineCount);
251
+ return { shouldContinue: true, lineCount: newLineCount };
252
+ }
253
+ return { shouldContinue: true, lineCount };
254
+ }
255
+ if (isDownArrow) {
256
+ if (state.selectedCommand < COMMANDS.length - 1) {
257
+ state.selectedCommand++;
258
+ if (state.selectedCommand >= state.scrollOffset + VISIBLE_COMMANDS) {
259
+ state.scrollOffset = state.selectedCommand - VISIBLE_COMMANDS + 1;
260
+ }
261
+ const newLineCount = render(state, lineCount);
262
+ return { shouldContinue: true, lineCount: newLineCount };
263
+ }
264
+ return { shouldContinue: true, lineCount };
265
+ }
266
+ }
267
+ return { shouldContinue: true, lineCount };
268
+ }
269
+ /**
270
+ * Show the tabbed help menu (in-place, not full screen)
271
+ * Returns a promise that resolves when the menu is closed
272
+ */
273
+ export function showHelpMenu() {
274
+ return new Promise((resolve) => {
275
+ const state = {
276
+ currentTab: 0,
277
+ selectedCommand: 0,
278
+ scrollOffset: 0,
279
+ };
280
+ // Track rendered lines for cleanup
281
+ let lineCount = 0;
282
+ // Start on a new line (menu appears below the prompt)
283
+ process.stdout.write('\n');
284
+ // Hide cursor during menu display
285
+ process.stdout.write(ANSI.HIDE_CURSOR);
286
+ // Store original raw mode state
287
+ const wasRawMode = process.stdin.isRaw;
288
+ // Enable raw mode
289
+ if (process.stdin.isTTY) {
290
+ process.stdin.setRawMode(true);
291
+ }
292
+ process.stdin.resume();
293
+ // Initial render
294
+ lineCount = render(state, 0);
295
+ const cleanup = () => {
296
+ // Clear the menu by moving up and clearing to end
297
+ // lineCount lines with lineCount-1 newlines between them, plus the initial \n we added
298
+ // So cursor is (lineCount-1)+1 = lineCount lines below where "You: /help" ended
299
+ if (lineCount > 0) {
300
+ process.stdout.write(ANSI.MOVE_UP(lineCount));
301
+ process.stdout.write(ANSI.CLEAR_TO_END);
302
+ }
303
+ // Print newline so next prompt appears on fresh line
304
+ process.stdout.write('\n');
305
+ // Show cursor
306
+ process.stdout.write(ANSI.SHOW_CURSOR);
307
+ // Restore raw mode to original state
308
+ if (process.stdin.isTTY && !wasRawMode) {
309
+ process.stdin.setRawMode(false);
310
+ }
311
+ process.stdin.removeListener('data', onData);
312
+ process.stdout.removeListener('resize', onResize);
313
+ };
314
+ const onData = (data) => {
315
+ const result = handleKey(data, state, lineCount);
316
+ lineCount = result.lineCount;
317
+ if (!result.shouldContinue) {
318
+ cleanup();
319
+ resolve();
320
+ }
321
+ };
322
+ const onResize = () => {
323
+ lineCount = render(state, lineCount);
324
+ };
325
+ process.stdin.on('data', onData);
326
+ process.stdout.on('resize', onResize);
327
+ });
328
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Backlog Template Generator
3
+ *
4
+ * Generates the task tracking file.
5
+ */
6
+ import type { ProjectConfig } from './types.js';
7
+ export declare function generateBacklogMd(config: ProjectConfig): string;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Backlog Template Generator
3
+ *
4
+ * Generates the task tracking file.
5
+ */
6
+ export function generateBacklogMd(config) {
7
+ return `# Backlog - ${config.name}
8
+
9
+ **Last Updated:** ${new Date().toISOString().split('T')[0]}
10
+ **Status Legend:** ✅ Completed | 🚧 In Progress | 📋 Planned | 🔮 Future
11
+
12
+ ---
13
+
14
+ ## 🎯 Current Sprint
15
+
16
+ *Add your first tasks here*
17
+
18
+ ### Phase 1: Setup & Foundation
19
+
20
+ | # | Task | Status | Notes |
21
+ |---|------|--------|-------|
22
+ | 1 | Project scaffolding | ✅ | Created by \`compilr init\` |
23
+ | 2 | Development environment setup | 📋 | |
24
+ | 3 | CI/CD pipeline | 📋 | |
25
+
26
+ ---
27
+
28
+ ## 📋 Backlog
29
+
30
+ ### Phase 2: Core Features
31
+
32
+ | # | Task | Status | Notes |
33
+ |---|------|--------|-------|
34
+ | 4 | *Define your first feature* | 📋 | |
35
+ | 5 | | 📋 | |
36
+ | 6 | | 📋 | |
37
+
38
+ ### Phase 3: Polish & Launch
39
+
40
+ | # | Task | Status | Notes |
41
+ |---|------|--------|-------|
42
+ | 7 | Testing | 📋 | |
43
+ | 8 | Documentation | 📋 | |
44
+ | 9 | Deploy to production | 📋 | |
45
+
46
+ ---
47
+
48
+ ## 🔮 Future Ideas
49
+
50
+ *Capture ideas for future development*
51
+
52
+ -
53
+ -
54
+ -
55
+
56
+ ---
57
+
58
+ ## 📊 Progress Summary
59
+
60
+ | Phase | Total | Done | Progress |
61
+ |-------|-------|------|----------|
62
+ | Phase 1 | 3 | 1 | 33% |
63
+ | Phase 2 | 0 | 0 | 0% |
64
+ | Phase 3 | 0 | 0 | 0% |
65
+
66
+ ---
67
+
68
+ ## Usage
69
+
70
+ ### Adding Tasks
71
+
72
+ Add tasks to the appropriate phase table:
73
+ \`\`\`markdown
74
+ | 10 | New task description | 📋 | Optional notes |
75
+ \`\`\`
76
+
77
+ ### Updating Status
78
+
79
+ Change the status emoji:
80
+ - 📋 → 🚧 when starting work
81
+ - 🚧 → ✅ when completed
82
+
83
+ ### Session Integration
84
+
85
+ Run \`compilr save\` at the end of each session to:
86
+ - Auto-update task statuses
87
+ - Generate session notes
88
+ - Track progress over time
89
+
90
+ ---
91
+
92
+ *This backlog is a living document. Reprioritize freely as requirements evolve.*
93
+ `;
94
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * CLAUDE.md Template Generator
3
+ *
4
+ * Generates the AI assistant context file.
5
+ */
6
+ import type { ProjectConfig } from './types.js';
7
+ export declare function generateClaudeMd(config: ProjectConfig): string;