@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.
- package/README.md +110 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +317 -0
- package/dist/agents/registry.d.ts +66 -0
- package/dist/agents/registry.js +238 -0
- package/dist/agents/types.d.ts +40 -0
- package/dist/agents/types.js +94 -0
- package/dist/commands/custom-registry.d.ts +69 -0
- package/dist/commands/custom-registry.js +246 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.js +7 -0
- package/dist/commands/types.d.ts +31 -0
- package/dist/commands/types.js +26 -0
- package/dist/commands.d.ts +63 -0
- package/dist/commands.js +324 -0
- package/dist/db/index.d.ts +42 -0
- package/dist/db/index.js +146 -0
- package/dist/db/repositories/document-repository.d.ts +63 -0
- package/dist/db/repositories/document-repository.js +184 -0
- package/dist/db/repositories/index.d.ts +9 -0
- package/dist/db/repositories/index.js +6 -0
- package/dist/db/repositories/project-repository.d.ts +132 -0
- package/dist/db/repositories/project-repository.js +337 -0
- package/dist/db/repositories/work-item-repository.d.ts +115 -0
- package/dist/db/repositories/work-item-repository.js +389 -0
- package/dist/db/schema.d.ts +83 -0
- package/dist/db/schema.js +143 -0
- package/dist/debug.d.ts +8 -0
- package/dist/debug.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +348 -0
- package/dist/index.old.d.ts +7 -0
- package/dist/index.old.js +1014 -0
- package/dist/repl.d.ts +121 -0
- package/dist/repl.js +1878 -0
- package/dist/settings/index.d.ts +80 -0
- package/dist/settings/index.js +195 -0
- package/dist/shared-handlers.d.ts +63 -0
- package/dist/shared-handlers.js +57 -0
- package/dist/slash-autocomplete.d.ts +41 -0
- package/dist/slash-autocomplete.js +638 -0
- package/dist/state.d.ts +75 -0
- package/dist/state.js +130 -0
- package/dist/tabbed-menu.d.ts +11 -0
- package/dist/tabbed-menu.js +328 -0
- package/dist/templates/backlog-md.d.ts +7 -0
- package/dist/templates/backlog-md.js +94 -0
- package/dist/templates/claude-md.d.ts +7 -0
- package/dist/templates/claude-md.js +189 -0
- package/dist/templates/coding-standards.d.ts +7 -0
- package/dist/templates/coding-standards.js +299 -0
- package/dist/templates/compilr-md.d.ts +7 -0
- package/dist/templates/compilr-md.js +189 -0
- package/dist/templates/config-json.d.ts +38 -0
- package/dist/templates/config-json.js +39 -0
- package/dist/templates/gitignore.d.ts +7 -0
- package/dist/templates/gitignore.js +85 -0
- package/dist/templates/index.d.ts +19 -0
- package/dist/templates/index.js +302 -0
- package/dist/templates/package-json.d.ts +7 -0
- package/dist/templates/package-json.js +111 -0
- package/dist/templates/readme-md.d.ts +7 -0
- package/dist/templates/readme-md.js +161 -0
- package/dist/templates/tsconfig.d.ts +7 -0
- package/dist/templates/tsconfig.js +61 -0
- package/dist/templates/types.d.ts +33 -0
- package/dist/templates/types.js +24 -0
- package/dist/test-autocomplete.d.ts +7 -0
- package/dist/test-autocomplete.js +85 -0
- package/dist/test-tabbed-menu.d.ts +7 -0
- package/dist/test-tabbed-menu.js +25 -0
- package/dist/themes/colors.d.ts +49 -0
- package/dist/themes/colors.js +135 -0
- package/dist/themes/index.d.ts +23 -0
- package/dist/themes/index.js +24 -0
- package/dist/themes/registry.d.ts +60 -0
- package/dist/themes/registry.js +195 -0
- package/dist/themes/types.d.ts +82 -0
- package/dist/themes/types.js +7 -0
- package/dist/tool-selector.d.ts +71 -0
- package/dist/tool-selector.js +184 -0
- package/dist/tools/ask-user-simple.d.ts +19 -0
- package/dist/tools/ask-user-simple.js +86 -0
- package/dist/tools/ask-user.d.ts +32 -0
- package/dist/tools/ask-user.js +113 -0
- package/dist/tools/backlog.d.ts +53 -0
- package/dist/tools/backlog.js +709 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.js +121 -0
- package/dist/ui/agents-overlay.d.ts +12 -0
- package/dist/ui/agents-overlay.js +501 -0
- package/dist/ui/arch-type-overlay.d.ts +20 -0
- package/dist/ui/arch-type-overlay.js +229 -0
- package/dist/ui/ask-user-overlay.d.ts +26 -0
- package/dist/ui/ask-user-overlay.js +647 -0
- package/dist/ui/ask-user-simple-overlay.d.ts +25 -0
- package/dist/ui/ask-user-simple-overlay.js +242 -0
- package/dist/ui/backlog-overlay.d.ts +17 -0
- package/dist/ui/backlog-overlay.js +786 -0
- package/dist/ui/commands-overlay.d.ts +11 -0
- package/dist/ui/commands-overlay.js +410 -0
- package/dist/ui/config-overlay.d.ts +34 -0
- package/dist/ui/config-overlay.js +977 -0
- package/dist/ui/conversation.d.ts +82 -0
- package/dist/ui/conversation.js +508 -0
- package/dist/ui/diff.d.ts +38 -0
- package/dist/ui/diff.js +182 -0
- package/dist/ui/ephemeral.d.ts +111 -0
- package/dist/ui/ephemeral.js +413 -0
- package/dist/ui/file-autocomplete.d.ts +45 -0
- package/dist/ui/file-autocomplete.js +237 -0
- package/dist/ui/footer.d.ts +153 -0
- package/dist/ui/footer.js +422 -0
- package/dist/ui/index.d.ts +12 -0
- package/dist/ui/index.js +15 -0
- package/dist/ui/init-overlay.d.ts +24 -0
- package/dist/ui/init-overlay.js +525 -0
- package/dist/ui/input-prompt-v2.d.ts +179 -0
- package/dist/ui/input-prompt-v2.js +991 -0
- package/dist/ui/input-prompt.d.ts +97 -0
- package/dist/ui/input-prompt.js +800 -0
- package/dist/ui/iteration-limit-overlay.d.ts +21 -0
- package/dist/ui/iteration-limit-overlay.js +150 -0
- package/dist/ui/keys-overlay.d.ts +14 -0
- package/dist/ui/keys-overlay.js +181 -0
- package/dist/ui/model-warning-overlay.d.ts +30 -0
- package/dist/ui/model-warning-overlay.js +171 -0
- package/dist/ui/overlay-controller.d.ts +25 -0
- package/dist/ui/overlay-controller.js +35 -0
- package/dist/ui/overlays.d.ts +47 -0
- package/dist/ui/overlays.js +627 -0
- package/dist/ui/permission-overlay.d.ts +16 -0
- package/dist/ui/permission-overlay.js +494 -0
- package/dist/ui/terminal.d.ts +117 -0
- package/dist/ui/terminal.js +237 -0
- package/dist/ui/todo-zone.d.ts +112 -0
- package/dist/ui/todo-zone.js +353 -0
- package/dist/ui/tools-overlay.d.ts +26 -0
- package/dist/ui/tools-overlay.js +278 -0
- package/dist/ui/tutorial-overlay.d.ts +10 -0
- package/dist/ui/tutorial-overlay.js +936 -0
- package/dist/ui/types.d.ts +103 -0
- package/dist/ui/types.js +33 -0
- package/dist/utils/credentials.d.ts +55 -0
- package/dist/utils/credentials.js +268 -0
- package/dist/utils/model-tiers.d.ts +37 -0
- package/dist/utils/model-tiers.js +118 -0
- package/dist/utils/project-memory.d.ts +47 -0
- package/dist/utils/project-memory.js +117 -0
- package/dist/utils/project-status.d.ts +56 -0
- package/dist/utils/project-status.js +237 -0
- package/package.json +66 -0
package/dist/state.d.ts
ADDED
|
@@ -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,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
|
+
}
|