nova-terminal-assistant 0.1.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.

Potentially problematic release.


This version of nova-terminal-assistant might be problematic. Click here for more details.

Files changed (192) hide show
  1. package/README.md +358 -0
  2. package/bin/nova +38 -0
  3. package/bin/nova.js +12 -0
  4. package/package.json +67 -0
  5. package/src/cli/commands/SmartCompletion.ts +458 -0
  6. package/src/cli/index.ts +5 -0
  7. package/src/cli/startup/IFlowRepl.ts +212 -0
  8. package/src/cli/startup/InkBasedRepl.ts +1056 -0
  9. package/src/cli/startup/InteractiveRepl.ts +2833 -0
  10. package/src/cli/startup/NovaApp.ts +1861 -0
  11. package/src/cli/startup/index.ts +4 -0
  12. package/src/cli/startup/parseArgs.ts +293 -0
  13. package/src/cli/test-modules.ts +27 -0
  14. package/src/cli/ui/IFlowDropdown.ts +425 -0
  15. package/src/cli/ui/ModernReplUI.ts +276 -0
  16. package/src/cli/ui/SimpleSelector2.ts +215 -0
  17. package/src/cli/ui/components/ConfirmDialog.ts +176 -0
  18. package/src/cli/ui/components/ErrorPanel.ts +364 -0
  19. package/src/cli/ui/components/InkAppRunner.tsx +67 -0
  20. package/src/cli/ui/components/InkComponents.tsx +613 -0
  21. package/src/cli/ui/components/NovaInkApp.tsx +312 -0
  22. package/src/cli/ui/components/ProgressBar.ts +177 -0
  23. package/src/cli/ui/components/ProgressIndicator.ts +298 -0
  24. package/src/cli/ui/components/QuickActions.ts +396 -0
  25. package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
  26. package/src/cli/ui/components/StatusBar.ts +194 -0
  27. package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
  28. package/src/cli/ui/components/index.ts +27 -0
  29. package/src/cli/ui/ink-prototype.tsx +347 -0
  30. package/src/cli/utils/CliUI.ts +336 -0
  31. package/src/cli/utils/CompletionHelper.ts +388 -0
  32. package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
  33. package/src/cli/utils/EnhancedCompleter.ts +513 -0
  34. package/src/cli/utils/ErrorEnhancer.ts +429 -0
  35. package/src/cli/utils/OutputFormatter.ts +193 -0
  36. package/src/cli/utils/index.ts +9 -0
  37. package/src/core/agents/AgentOrchestrator.ts +515 -0
  38. package/src/core/agents/index.ts +17 -0
  39. package/src/core/audit/AuditLogger.ts +509 -0
  40. package/src/core/audit/index.ts +11 -0
  41. package/src/core/auth/AuthManager.d.ts.map +1 -0
  42. package/src/core/auth/AuthManager.ts +138 -0
  43. package/src/core/auth/index.d.ts.map +1 -0
  44. package/src/core/auth/index.ts +2 -0
  45. package/src/core/config/ConfigManager.d.ts.map +1 -0
  46. package/src/core/config/ConfigManager.test.ts +183 -0
  47. package/src/core/config/ConfigManager.ts +1219 -0
  48. package/src/core/config/index.d.ts.map +1 -0
  49. package/src/core/config/index.ts +1 -0
  50. package/src/core/context/ContextBuilder.d.ts.map +1 -0
  51. package/src/core/context/ContextBuilder.ts +171 -0
  52. package/src/core/context/ContextCompressor.d.ts.map +1 -0
  53. package/src/core/context/ContextCompressor.ts +642 -0
  54. package/src/core/context/LayeredMemoryManager.ts +657 -0
  55. package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
  56. package/src/core/context/MemoryDiscovery.ts +175 -0
  57. package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
  58. package/src/core/context/defaultSystemPrompt.ts +35 -0
  59. package/src/core/context/index.d.ts.map +1 -0
  60. package/src/core/context/index.ts +22 -0
  61. package/src/core/extensions/SkillGenerator.ts +421 -0
  62. package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
  63. package/src/core/extensions/SkillInstaller.ts +257 -0
  64. package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
  65. package/src/core/extensions/SkillRegistry.ts +361 -0
  66. package/src/core/extensions/SkillValidator.ts +525 -0
  67. package/src/core/extensions/index.ts +15 -0
  68. package/src/core/index.d.ts.map +1 -0
  69. package/src/core/index.ts +42 -0
  70. package/src/core/mcp/McpManager.d.ts.map +1 -0
  71. package/src/core/mcp/McpManager.ts +632 -0
  72. package/src/core/mcp/index.d.ts.map +1 -0
  73. package/src/core/mcp/index.ts +2 -0
  74. package/src/core/model/ModelClient.d.ts.map +1 -0
  75. package/src/core/model/ModelClient.ts +217 -0
  76. package/src/core/model/ModelConnectionTester.ts +363 -0
  77. package/src/core/model/ModelValidator.ts +348 -0
  78. package/src/core/model/index.d.ts.map +1 -0
  79. package/src/core/model/index.ts +6 -0
  80. package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
  81. package/src/core/model/providers/AnthropicProvider.ts +279 -0
  82. package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
  83. package/src/core/model/providers/CodingPlanProvider.ts +210 -0
  84. package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
  85. package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
  86. package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
  87. package/src/core/model/providers/OllamaManager.ts +201 -0
  88. package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
  89. package/src/core/model/providers/OllamaProvider.ts +73 -0
  90. package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  91. package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
  92. package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
  93. package/src/core/model/providers/OpenAIProvider.ts +29 -0
  94. package/src/core/model/providers/index.d.ts.map +1 -0
  95. package/src/core/model/providers/index.ts +12 -0
  96. package/src/core/model/types.d.ts.map +1 -0
  97. package/src/core/model/types.ts +77 -0
  98. package/src/core/security/ApprovalManager.d.ts.map +1 -0
  99. package/src/core/security/ApprovalManager.ts +174 -0
  100. package/src/core/security/FileFilter.d.ts.map +1 -0
  101. package/src/core/security/FileFilter.ts +141 -0
  102. package/src/core/security/HookExecutor.d.ts.map +1 -0
  103. package/src/core/security/HookExecutor.ts +178 -0
  104. package/src/core/security/SandboxExecutor.ts +447 -0
  105. package/src/core/security/index.d.ts.map +1 -0
  106. package/src/core/security/index.ts +8 -0
  107. package/src/core/session/AgentLoop.d.ts.map +1 -0
  108. package/src/core/session/AgentLoop.ts +501 -0
  109. package/src/core/session/SessionManager.d.ts.map +1 -0
  110. package/src/core/session/SessionManager.test.ts +183 -0
  111. package/src/core/session/SessionManager.ts +460 -0
  112. package/src/core/session/index.d.ts.map +1 -0
  113. package/src/core/session/index.ts +3 -0
  114. package/src/core/telemetry/Telemetry.d.ts.map +1 -0
  115. package/src/core/telemetry/Telemetry.ts +90 -0
  116. package/src/core/telemetry/TelemetryService.ts +531 -0
  117. package/src/core/telemetry/index.d.ts.map +1 -0
  118. package/src/core/telemetry/index.ts +12 -0
  119. package/src/core/testing/AutoFixer.ts +385 -0
  120. package/src/core/testing/ErrorAnalyzer.ts +499 -0
  121. package/src/core/testing/TestRunner.ts +265 -0
  122. package/src/core/testing/agent-cli-tests.ts +538 -0
  123. package/src/core/testing/index.ts +11 -0
  124. package/src/core/tools/ToolRegistry.d.ts.map +1 -0
  125. package/src/core/tools/ToolRegistry.test.ts +206 -0
  126. package/src/core/tools/ToolRegistry.ts +260 -0
  127. package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
  128. package/src/core/tools/impl/EditFileTool.ts +97 -0
  129. package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
  130. package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
  131. package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
  132. package/src/core/tools/impl/MemoryTool.ts +102 -0
  133. package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
  134. package/src/core/tools/impl/ReadFileTool.ts +58 -0
  135. package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
  136. package/src/core/tools/impl/SearchContentTool.ts +94 -0
  137. package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
  138. package/src/core/tools/impl/SearchFileTool.ts +61 -0
  139. package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
  140. package/src/core/tools/impl/ShellTool.ts +118 -0
  141. package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
  142. package/src/core/tools/impl/TaskTool.ts +207 -0
  143. package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
  144. package/src/core/tools/impl/TodoTool.ts +122 -0
  145. package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
  146. package/src/core/tools/impl/WebFetchTool.ts +103 -0
  147. package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
  148. package/src/core/tools/impl/WebSearchTool.ts +89 -0
  149. package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
  150. package/src/core/tools/impl/WriteFileTool.ts +49 -0
  151. package/src/core/tools/impl/index.d.ts.map +1 -0
  152. package/src/core/tools/impl/index.ts +16 -0
  153. package/src/core/tools/index.d.ts.map +1 -0
  154. package/src/core/tools/index.ts +7 -0
  155. package/src/core/tools/schemas/execution.d.ts.map +1 -0
  156. package/src/core/tools/schemas/execution.ts +42 -0
  157. package/src/core/tools/schemas/file.d.ts.map +1 -0
  158. package/src/core/tools/schemas/file.ts +119 -0
  159. package/src/core/tools/schemas/index.d.ts.map +1 -0
  160. package/src/core/tools/schemas/index.ts +11 -0
  161. package/src/core/tools/schemas/memory.d.ts.map +1 -0
  162. package/src/core/tools/schemas/memory.ts +52 -0
  163. package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
  164. package/src/core/tools/schemas/orchestration.ts +44 -0
  165. package/src/core/tools/schemas/search.d.ts.map +1 -0
  166. package/src/core/tools/schemas/search.ts +112 -0
  167. package/src/core/tools/schemas/todo.d.ts.map +1 -0
  168. package/src/core/tools/schemas/todo.ts +32 -0
  169. package/src/core/tools/schemas/web.d.ts.map +1 -0
  170. package/src/core/tools/schemas/web.ts +86 -0
  171. package/src/core/types/config.d.ts.map +1 -0
  172. package/src/core/types/config.ts +200 -0
  173. package/src/core/types/errors.d.ts.map +1 -0
  174. package/src/core/types/errors.ts +204 -0
  175. package/src/core/types/index.d.ts.map +1 -0
  176. package/src/core/types/index.ts +8 -0
  177. package/src/core/types/session.d.ts.map +1 -0
  178. package/src/core/types/session.ts +216 -0
  179. package/src/core/types/tools.d.ts.map +1 -0
  180. package/src/core/types/tools.ts +157 -0
  181. package/src/core/utils/CheckpointManager.d.ts.map +1 -0
  182. package/src/core/utils/CheckpointManager.ts +327 -0
  183. package/src/core/utils/Logger.d.ts.map +1 -0
  184. package/src/core/utils/Logger.ts +98 -0
  185. package/src/core/utils/RetryManager.ts +471 -0
  186. package/src/core/utils/TokenCounter.d.ts.map +1 -0
  187. package/src/core/utils/TokenCounter.ts +414 -0
  188. package/src/core/utils/VectorMemoryStore.ts +440 -0
  189. package/src/core/utils/helpers.d.ts.map +1 -0
  190. package/src/core/utils/helpers.ts +89 -0
  191. package/src/core/utils/index.d.ts.map +1 -0
  192. package/src/core/utils/index.ts +19 -0
@@ -0,0 +1,207 @@
1
+ // ============================================================================
2
+ // Task Tool - Launch sub-agents for parallel task execution
3
+ // ============================================================================
4
+
5
+ import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
6
+ import { ToolError } from '../../types/errors.js';
7
+ import { AgentLoop } from '../../session/AgentLoop.js';
8
+ import { SessionManager } from '../../session/SessionManager.js';
9
+ import { ToolRegistry } from '../../tools/ToolRegistry.js';
10
+ import { ModelClient } from '../../model/ModelClient.js';
11
+ import { buildSystemPrompt } from '../../context/defaultSystemPrompt.js';
12
+ import type { ContextCompressor } from '../../context/ContextCompressor.js';
13
+
14
+ interface TaskInput {
15
+ description: string;
16
+ prompt: string;
17
+ subagentType: 'code-explorer' | 'research' | 'executor';
18
+ maxTurns?: number;
19
+ mode?: 'acceptEdits' | 'bypassPermissions' | 'default' | 'plan';
20
+ name?: string;
21
+ teamName?: string;
22
+ }
23
+
24
+ /**
25
+ * Task handler - launches a sub-agent to perform a specific task
26
+ *
27
+ * This enables parallel task execution and specialized agent behaviors:
28
+ * - code-explorer: Analyzes codebases and provides insights
29
+ * - research: Gathers information from web and documentation
30
+ * - executor: Performs actions and executes commands
31
+ */
32
+ export const taskHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
33
+ // Validate required fields
34
+ const { description, prompt, subagentType, maxTurns = 10, mode = 'default', name } = input.params as Record<string, unknown>;
35
+
36
+ if (typeof description !== 'string' || typeof prompt !== 'string' || typeof subagentType !== 'string') {
37
+ throw new ToolError('Task requires description, prompt, and subagentType parameters to be strings', 'task');
38
+ }
39
+
40
+ if (!description || !prompt || !subagentType) {
41
+ throw new ToolError('Task requires description, prompt, and subagentType parameters', 'task');
42
+ }
43
+
44
+ const params: TaskInput = {
45
+ description,
46
+ prompt,
47
+ subagentType: subagentType as TaskInput['subagentType'],
48
+ maxTurns: typeof maxTurns === 'number' ? maxTurns : 10,
49
+ mode: typeof mode === 'string' ? mode as TaskInput['mode'] : 'default',
50
+ name: typeof name === 'string' ? name : undefined,
51
+ };
52
+
53
+ const sessionManager = input.context.sessionManager as SessionManager;
54
+ const toolRegistry = input.context.toolRegistry as ToolRegistry;
55
+ const modelClient = input.context.modelClient as ModelClient;
56
+ const contextCompressor = input.context.contextCompressor as ContextCompressor;
57
+
58
+ if (!sessionManager || !toolRegistry || !modelClient) {
59
+ throw new ToolError('Task tool requires sessionManager, toolRegistry, and modelClient in context', 'task');
60
+ }
61
+
62
+ // Create a new session for the sub-agent
63
+ const subSession = sessionManager.create({
64
+ workingDirectory: input.context.workingDirectory,
65
+ model: modelClient.getModel(),
66
+ streaming: false,
67
+ parentSessionId: input.context.sessionId,
68
+ });
69
+
70
+ // Configure approval mode based on task mode
71
+ let approvalMode = input.context.approvalMode || 'plan';
72
+ if (mode === 'acceptEdits') {
73
+ approvalMode = 'plan';
74
+ } else if (mode === 'bypassPermissions') {
75
+ approvalMode = 'yolo';
76
+ }
77
+
78
+ // Build system prompt for sub-agent
79
+ const subSystemPrompt = buildSystemPrompt({
80
+ workingDirectory: input.context.workingDirectory,
81
+ model: modelClient.getModel(),
82
+ approvalMode,
83
+ });
84
+
85
+ // Add sub-agent specialization to system prompt
86
+ const specializations: Record<string, string> = {
87
+ 'code-explorer': `You are a Code Explorer agent. Your specialty is analyzing codebases, understanding architecture, identifying patterns, and providing code insights.
88
+ Focus on:
89
+ - Code structure and organization
90
+ - Design patterns and architecture
91
+ - Potential issues and improvements
92
+ - Dependencies and relationships
93
+
94
+ Be thorough in your analysis and provide actionable insights.`,
95
+
96
+ 'research': `You are a Research agent. Your specialty is gathering information, investigating problems, and finding solutions.
97
+ Focus on:
98
+ - Searching for relevant documentation and examples
99
+ - Investigating error messages and issues
100
+ - Finding best practices and patterns
101
+ - Gathering context and background information
102
+
103
+ Be comprehensive in your research and cite sources when possible.`,
104
+
105
+ 'executor': `You are an Executor agent. Your specialty is performing tasks, running commands, and making changes.
106
+ Focus on:
107
+ - Executing commands and scripts
108
+ - Modifying files and code
109
+ - Running tests and builds
110
+ - Implementing solutions
111
+
112
+ Be careful and precise in your actions, following best practices.`
113
+ };
114
+
115
+ const specializedSystemPrompt = subSystemPrompt + '\n\n' + specializations[subagentType];
116
+
117
+ // Create agent loop for sub-agent
118
+ const agentLoop = new AgentLoop({
119
+ modelClient,
120
+ sessionManager,
121
+ toolRegistry,
122
+ systemPrompt: specializedSystemPrompt,
123
+ contextCompressor,
124
+ maxContextTokens: input.context.maxTokens * 8 || 128000,
125
+ onApprovalRequired: input.context.onApprovalRequired,
126
+ });
127
+
128
+ // Run the sub-agent
129
+ const result = await agentLoop.run(subSession.id, prompt);
130
+
131
+ // Get all messages from the sub-session
132
+ const messages = sessionManager.getMessages(subSession.id);
133
+ const assistantMessages = messages.filter(m => m.role === 'assistant');
134
+
135
+ // Extract the final response
136
+ let finalResponse = '';
137
+ if (assistantMessages.length > 0) {
138
+ const lastMessage = assistantMessages[assistantMessages.length - 1];
139
+ const textBlocks = lastMessage.content.filter(block => block.type === 'text');
140
+ finalResponse = textBlocks.map(block => (block as any).text).join('\n\n');
141
+ }
142
+
143
+ // Clean up sub-session
144
+ sessionManager.delete(subSession.id);
145
+
146
+ // Format the result
147
+ const output = `
148
+ ### 🤖 Sub-agent Task Complete
149
+
150
+ **Task**: ${description}
151
+ **Type**: ${subagentType}
152
+ **Agent**: ${name || 'Unnamed'}
153
+ **Turns**: ${result.turnsCompleted}
154
+ **Tokens**: ${result.totalInputTokens + result.totalOutputTokens}
155
+
156
+ ---
157
+
158
+ ${finalResponse}
159
+
160
+ ---
161
+
162
+ **Status**: ✅ Completed successfully
163
+ `.trim();
164
+
165
+ return { content: output };
166
+ };
167
+
168
+ export const taskSchema = {
169
+ type: 'object' as const,
170
+ properties: {
171
+ description: {
172
+ type: 'string',
173
+ description: 'Brief description of the task (3-5 words)',
174
+ },
175
+ prompt: {
176
+ type: 'string',
177
+ description: 'Detailed task description for the sub-agent',
178
+ },
179
+ subagentType: {
180
+ type: 'string',
181
+ description: 'Type of sub-agent to spawn',
182
+ enum: ['code-explorer', 'research', 'executor'],
183
+ default: 'research',
184
+ },
185
+ maxTurns: {
186
+ type: 'number',
187
+ description: 'Maximum number of agentic turns',
188
+ default: 10,
189
+ },
190
+ mode: {
191
+ type: 'string',
192
+ description: 'Permission mode for the sub-agent',
193
+ enum: ['acceptEdits', 'bypassPermissions', 'default', 'plan'],
194
+ default: 'default',
195
+ },
196
+ name: {
197
+ type: 'string',
198
+ description: 'Name for the sub-agent (enables team mode)',
199
+ },
200
+ teamName: {
201
+ type: 'string',
202
+ description: 'Team to join (enables team mode)',
203
+ },
204
+ },
205
+ required: ['description', 'prompt', 'subagentType'],
206
+ additionalProperties: false,
207
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TodoTool.d.ts","sourceRoot":"","sources":["TodoTool.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AA6B7F,eAAO,MAAM,WAAW,EAAE,WA2DzB,CAAC;AAEF,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BtB,CAAC"}
@@ -0,0 +1,122 @@
1
+ // ============================================================================
2
+ // Todo Tool - Task tracking and progress display
3
+ // ============================================================================
4
+
5
+ import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
6
+ import { ToolError } from '../../types/errors.js';
7
+
8
+ interface TodoItem {
9
+ id: string;
10
+ content: string;
11
+ status: 'pending' | 'in_progress' | 'completed';
12
+ }
13
+
14
+ /** In-memory todo storage per session */
15
+ const sessionTodos = new Map<string, TodoItem[]>();
16
+
17
+ function getTodos(sessionId: string): TodoItem[] {
18
+ if (!sessionTodos.has(sessionId)) {
19
+ sessionTodos.set(sessionId, []);
20
+ }
21
+ return sessionTodos.get(sessionId)!;
22
+ }
23
+
24
+ function renderTodos(todos: TodoItem[]): string {
25
+ if (todos.length === 0) return 'No tasks tracked.';
26
+ const statusIcons: Record<string, string> = {
27
+ pending: '○',
28
+ in_progress: '◉',
29
+ completed: '●',
30
+ };
31
+ return todos.map((t) => `${statusIcons[t.status]} [${t.status.padEnd(12)}] ${t.content}`).join('\n');
32
+ }
33
+
34
+ export const todoHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
35
+ const { action, items, id, content, status } = input.params as {
36
+ action?: string;
37
+ items?: string;
38
+ id?: string;
39
+ content?: string;
40
+ status?: string;
41
+ };
42
+ const sessionId = input.context.sessionId;
43
+ const todos = getTodos(sessionId);
44
+
45
+ switch (action) {
46
+ case 'list': {
47
+ return { content: renderTodos(todos) };
48
+ }
49
+
50
+ case 'create': {
51
+ if (!items) {
52
+ throw new ToolError('Missing "items" parameter. Provide a JSON array of task strings.', 'todo');
53
+ }
54
+ let parsed: unknown[];
55
+ try {
56
+ parsed = JSON.parse(typeof items === 'string' ? items : JSON.stringify(items));
57
+ } catch {
58
+ parsed = [String(items)];
59
+ }
60
+ for (const item of parsed) {
61
+ todos.push({ id: `t${todos.length + 1}`, content: String(item), status: 'pending' });
62
+ }
63
+ return { content: `Added ${parsed.length} task(s).\n${renderTodos(todos)}` };
64
+ }
65
+
66
+ case 'update': {
67
+ if (!id || !status) {
68
+ throw new ToolError('Missing "id" or "status" parameter.', 'todo');
69
+ }
70
+ const target = todos.find((t) => t.id === id || t.id === `t${id}`);
71
+ if (!target) {
72
+ throw new ToolError(`Task "${id}" not found.`, 'todo');
73
+ }
74
+ const validStatuses = ['pending', 'in_progress', 'completed'];
75
+ if (!validStatuses.includes(status)) {
76
+ throw new ToolError(`Invalid status "${status}". Use: ${validStatuses.join(', ')}`, 'todo');
77
+ }
78
+ if (content) target.content = content;
79
+ target.status = status as TodoItem['status'];
80
+ return { content: renderTodos(todos) };
81
+ }
82
+
83
+ case 'clear': {
84
+ todos.length = 0;
85
+ return { content: 'All tasks cleared.' };
86
+ }
87
+
88
+ default: {
89
+ // If no action, show current state
90
+ return { content: renderTodos(todos) };
91
+ }
92
+ }
93
+ };
94
+
95
+ export const todoSchema = {
96
+ type: 'object' as const,
97
+ properties: {
98
+ action: {
99
+ type: 'string',
100
+ enum: ['list', 'create', 'update', 'clear'],
101
+ description: 'Action to perform: list (show tasks), create (add tasks), update (change status), clear (remove all)',
102
+ },
103
+ items: {
104
+ type: 'string',
105
+ description: 'JSON array of task strings to create (for action=create). Example: \'["Task 1", "Task 2"]\'',
106
+ },
107
+ id: {
108
+ type: 'string',
109
+ description: 'Task ID to update (for action=update)',
110
+ },
111
+ content: {
112
+ type: 'string',
113
+ description: 'New content for the task (for action=update)',
114
+ },
115
+ status: {
116
+ type: 'string',
117
+ enum: ['pending', 'in_progress', 'completed'],
118
+ description: 'New status for the task (for action=update)',
119
+ },
120
+ },
121
+ additionalProperties: false,
122
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebFetchTool.d.ts","sourceRoot":"","sources":["WebFetchTool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AAG7F,eAAO,MAAM,eAAe,EAAE,WA+E7B,CAAC"}
@@ -0,0 +1,103 @@
1
+ import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
2
+ import { ToolError, TimeoutError } from '../../types/errors.js';
3
+
4
+ export const webFetchHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
5
+ const {
6
+ url,
7
+ method = 'GET',
8
+ headers: extraHeaders,
9
+ body,
10
+ timeout = 15000,
11
+ followRedirects = true,
12
+ maxLength = 50000,
13
+ } = input.params as {
14
+ url: string;
15
+ method?: string;
16
+ headers?: Record<string, string>;
17
+ body?: string;
18
+ timeout?: number;
19
+ followRedirects?: boolean;
20
+ maxLength?: number;
21
+ };
22
+
23
+ try {
24
+ // Basic URL validation
25
+ const parsedUrl = new URL(url);
26
+ if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
27
+ throw new Error(`Unsupported protocol: ${parsedUrl.protocol}`);
28
+ }
29
+
30
+ const controller = new AbortController();
31
+ const timer = setTimeout(() => controller.abort(), timeout);
32
+
33
+ const response = await fetch(url, {
34
+ method,
35
+ headers: {
36
+ 'User-Agent': 'NovaCLI/0.1.0 (Research Agent)',
37
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,text/markdown;q=0.9,*/*;q=0.8',
38
+ ...extraHeaders,
39
+ },
40
+ body: method !== 'GET' ? body : undefined,
41
+ signal: controller.signal,
42
+ redirect: followRedirects ? 'follow' : 'manual',
43
+ });
44
+
45
+ clearTimeout(timer);
46
+
47
+ if (!response.ok) {
48
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
49
+ }
50
+
51
+ const contentType = response.headers.get('content-type') || '';
52
+ let content = await response.text();
53
+
54
+ // Truncate if too long
55
+ if (content.length > maxLength) {
56
+ content = content.slice(0, maxLength) + `\n\n[Content truncated: showing ${maxLength} of ${content.length} characters]`;
57
+ }
58
+
59
+ // Basic HTML to text conversion
60
+ if (contentType.includes('text/html')) {
61
+ content = htmlToText(content);
62
+ }
63
+
64
+ return {
65
+ content,
66
+ metadata: {
67
+ url,
68
+ status: response.status,
69
+ contentType,
70
+ contentLength: content.length,
71
+ finalUrl: response.url,
72
+ },
73
+ };
74
+ } catch (err) {
75
+ if (err instanceof DOMException && err.name === 'AbortError') {
76
+ throw new TimeoutError(`Fetch timed out after ${timeout}ms`, timeout);
77
+ }
78
+ if (err instanceof TypeError && (err as Error).message.includes('URL')) {
79
+ throw new ToolError(`Invalid URL: ${url}`, 'web_fetch');
80
+ }
81
+ throw new ToolError(`Failed to fetch URL: ${(err as Error).message}`, 'web_fetch');
82
+ }
83
+ };
84
+
85
+ /** Basic HTML to text converter */
86
+ function htmlToText(html: string): string {
87
+ return html
88
+ // Remove scripts and styles
89
+ .replace(/<script[\s\S]*?<\/script>/gi, '')
90
+ .replace(/<style[\s\S]*?<\/style>/gi, '')
91
+ // Remove HTML tags
92
+ .replace(/<[^>]+>/g, ' ')
93
+ // Decode HTML entities
94
+ .replace(/&amp;/g, '&')
95
+ .replace(/&lt;/g, '<')
96
+ .replace(/&gt;/g, '>')
97
+ .replace(/&quot;/g, '"')
98
+ .replace(/&#39;/g, "'")
99
+ .replace(/&nbsp;/g, ' ')
100
+ // Collapse whitespace
101
+ .replace(/\s+/g, ' ')
102
+ .trim();
103
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSearchTool.d.ts","sourceRoot":"","sources":["WebSearchTool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AAG7F,eAAO,MAAM,gBAAgB,EAAE,WAqF9B,CAAC"}
@@ -0,0 +1,89 @@
1
+ import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
2
+ import { ToolError } from '../../types/errors.js';
3
+
4
+ export const webSearchHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
5
+ const { query, maxResults = 10, language, region, safeSearch = true, freshness } = input.params as {
6
+ query: string;
7
+ maxResults?: number;
8
+ language?: string;
9
+ region?: string;
10
+ safeSearch?: boolean;
11
+ freshness?: string;
12
+ };
13
+
14
+ // This is a placeholder implementation. In production, integrate with a search API
15
+ // such as Google Custom Search, Bing Web Search, or Serper API.
16
+
17
+ const apiKey = process.env.NOVA_SEARCH_API_KEY || process.env.SERPER_API_KEY;
18
+
19
+ if (!apiKey) {
20
+ // Fallback: provide a helpful message about configuration
21
+ return {
22
+ content: `[Web Search Placeholder]\n\nQuery: ${query}\n\nTo enable web search, configure one of:\n- NOVA_SEARCH_API_KEY: Set to a Serper.dev API key\n- SERPER_API_KEY: Alternative Serper key\n\nGet a Serper API key at: https://serper.dev/\n\nConfiguration can be set in:\n- Environment variables\n- ~/.nova/config.yaml (search.apiKey)\n- Project .nova/config.yaml`,
23
+ metadata: { query, configured: false },
24
+ };
25
+ }
26
+
27
+ try {
28
+ // Serper.dev API integration
29
+ const response = await fetch('https://google.serper.dev/search', {
30
+ method: 'POST',
31
+ headers: {
32
+ 'X-API-KEY': apiKey,
33
+ 'Content-Type': 'application/json',
34
+ },
35
+ body: JSON.stringify({
36
+ q: query,
37
+ num: maxResults,
38
+ gl: region,
39
+ hl: language,
40
+ safe: safeSearch ? 'active' : 'off',
41
+ tbs: freshness ? `qdr:${freshness}` : undefined,
42
+ }),
43
+ });
44
+
45
+ if (!response.ok) {
46
+ throw new Error(`Search API returned ${response.status}: ${response.statusText}`);
47
+ }
48
+
49
+ const data = await response.json() as Record<string, unknown>;
50
+ const organic = (data.organic || []) as Array<{
51
+ title: string;
52
+ link: string;
53
+ snippet: string;
54
+ position: number;
55
+ }>;
56
+
57
+ if (organic.length === 0) {
58
+ return {
59
+ content: `No results found for: "${query}"`,
60
+ metadata: { query, count: 0 },
61
+ };
62
+ }
63
+
64
+ const results = organic
65
+ .map((r, i) => `${i + 1}. ${r.title}\n ${r.link}\n ${r.snippet}`)
66
+ .join('\n\n');
67
+
68
+ const knowledgeGraph = data.knowledgeGraph as { title?: string; description?: string } | undefined;
69
+ let output = '';
70
+ if (knowledgeGraph?.title) {
71
+ output += `Knowledge Graph: ${knowledgeGraph.title}\n`;
72
+ if (knowledgeGraph.description) output += `${knowledgeGraph.description}\n`;
73
+ output += '\n';
74
+ }
75
+
76
+ output += results;
77
+
78
+ return {
79
+ content: output,
80
+ metadata: {
81
+ query,
82
+ count: organic.length,
83
+ hasKnowledgeGraph: !!knowledgeGraph,
84
+ },
85
+ };
86
+ } catch (err) {
87
+ throw new ToolError(`Web search failed: ${(err as Error).message}`, 'web_search');
88
+ }
89
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WriteFileTool.d.ts","sourceRoot":"","sources":["WriteFileTool.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAuC,MAAM,sBAAsB,CAAC;AAG7F,eAAO,MAAM,gBAAgB,EAAE,WA2C9B,CAAC"}
@@ -0,0 +1,49 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import type { ToolHandler, ToolHandlerInput, ToolHandlerOutput } from '../../types/tools.js';
4
+ import { ToolError } from '../../types/errors.js';
5
+
6
+ export const writeFileHandler: ToolHandler = async (input: ToolHandlerInput): Promise<ToolHandlerOutput> => {
7
+ const { filePath, content, createDirectories = false, encoding = 'utf-8' } = input.params as {
8
+ filePath: string;
9
+ content: string;
10
+ createDirectories?: boolean;
11
+ encoding?: BufferEncoding;
12
+ };
13
+
14
+ const resolvedPath = path.resolve(filePath);
15
+
16
+ try {
17
+ if (createDirectories) {
18
+ const dir = path.dirname(resolvedPath);
19
+ await fs.mkdir(dir, { recursive: true });
20
+ }
21
+
22
+ // Verify parent directory exists
23
+ const dir = path.dirname(resolvedPath);
24
+ try {
25
+ await fs.access(dir, fs.constants.W_OK);
26
+ } catch {
27
+ throw new ToolError(`Cannot write to directory: ${dir}`, 'write_file');
28
+ }
29
+
30
+ await fs.writeFile(resolvedPath, content, { encoding });
31
+
32
+ return {
33
+ content: `Successfully wrote ${content.length} characters to ${resolvedPath}`,
34
+ metadata: {
35
+ path: resolvedPath,
36
+ size: Buffer.byteLength(content, encoding),
37
+ encoding,
38
+ },
39
+ filesAffected: [resolvedPath],
40
+ };
41
+ } catch (err) {
42
+ if (err instanceof ToolError) throw err;
43
+ const code = (err as NodeJS.ErrnoException).code;
44
+ if (code === 'EACCES') {
45
+ throw new ToolError(`Permission denied: ${filePath}`, 'write_file', undefined, { code });
46
+ }
47
+ throw new ToolError(`Failed to write file: ${(err as Error).message}`, 'write_file');
48
+ }
49
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,16 @@
1
+ // ============================================================================
2
+ // Tool Implementations Barrel Export
3
+ // ============================================================================
4
+
5
+ export { readFileHandler } from './ReadFileTool.js';
6
+ export { writeFileHandler } from './WriteFileTool.js';
7
+ export { editFileHandler } from './EditFileTool.js';
8
+ export { listDirectoryHandler } from './ListDirectoryTool.js';
9
+ export { searchFileHandler } from './SearchFileTool.js';
10
+ export { searchContentHandler } from './SearchContentTool.js';
11
+ export { shellHandler } from './ShellTool.js';
12
+ export { webSearchHandler } from './WebSearchTool.js';
13
+ export { webFetchHandler } from './WebFetchTool.js';
14
+ export { memoryReadHandler, memoryWriteHandler } from './MemoryTool.js';
15
+ export { todoHandler } from './TodoTool.js';
16
+ export { taskHandler } from './TaskTool.js';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,7 @@
1
+ // ============================================================================
2
+ // Tools Package Barrel Export
3
+ // ============================================================================
4
+
5
+ export { ToolRegistry } from './ToolRegistry.js';
6
+ export * from './schemas/index.js';
7
+ export * from './impl/index.js';
@@ -0,0 +1 @@
1
+ {"version":3,"file":"execution.d.ts","sourceRoot":"","sources":["execution.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqChC,CAAC"}
@@ -0,0 +1,42 @@
1
+ // ============================================================================
2
+ // Execution Tool Schemas - JSON schemas for shell command execution
3
+ // ============================================================================
4
+
5
+ export const executeCommandSchema = {
6
+ type: 'object' as const,
7
+ properties: {
8
+ command: {
9
+ type: 'string',
10
+ description: 'The shell command to execute',
11
+ },
12
+ workingDirectory: {
13
+ type: 'string',
14
+ description: 'Working directory for the command (overrides session default)',
15
+ },
16
+ env: {
17
+ type: 'object',
18
+ description: 'Additional environment variables for the command',
19
+ additionalProperties: { type: 'string' },
20
+ },
21
+ timeout: {
22
+ type: 'number',
23
+ description: 'Timeout in milliseconds (default: 30000)',
24
+ default: 30000,
25
+ },
26
+ shell: {
27
+ type: 'string',
28
+ description: 'Shell to use (e.g., "bash", "powershell", "cmd")',
29
+ },
30
+ captureStderr: {
31
+ type: 'boolean',
32
+ description: 'Whether to capture stderr separately',
33
+ default: true,
34
+ },
35
+ input: {
36
+ type: 'string',
37
+ description: 'Stdin input for the command',
38
+ },
39
+ },
40
+ required: ['command'],
41
+ additionalProperties: false,
42
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["file.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;CAuB1B,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;CAwB3B,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4B1B,CAAC;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiC/B,CAAC"}