codeep 1.2.12 → 1.2.13

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 (43) hide show
  1. package/bin/codeep.js +14 -0
  2. package/dist/api/index.js +7 -7
  3. package/dist/config/index.js +3 -3
  4. package/dist/config/providers.test.js +1 -1
  5. package/dist/hooks/index.js +1 -1
  6. package/dist/hooks/useAgent.js +3 -3
  7. package/dist/renderer/App.js +8 -8
  8. package/dist/renderer/ChatUI.js +3 -3
  9. package/dist/renderer/Screen.js +1 -1
  10. package/dist/renderer/components/Export.js +1 -1
  11. package/dist/renderer/components/Help.js +1 -1
  12. package/dist/renderer/components/Intro.js +1 -1
  13. package/dist/renderer/components/Login.js +3 -3
  14. package/dist/renderer/components/Logout.js +1 -1
  15. package/dist/renderer/components/Modal.js +2 -2
  16. package/dist/renderer/components/Permission.js +2 -2
  17. package/dist/renderer/components/Search.js +1 -1
  18. package/dist/renderer/components/SelectScreen.js +1 -1
  19. package/dist/renderer/components/Settings.js +3 -3
  20. package/dist/renderer/components/Status.js +1 -1
  21. package/dist/renderer/demo-app.js +1 -1
  22. package/dist/renderer/demo.js +1 -1
  23. package/dist/renderer/index.js +9 -9
  24. package/dist/renderer/main.js +31 -31
  25. package/dist/utils/agent.js +8 -8
  26. package/dist/utils/agent.test.js +1 -1
  27. package/dist/utils/codeReview.js +1 -1
  28. package/dist/utils/context.js +1 -1
  29. package/dist/utils/git.test.js +1 -1
  30. package/dist/utils/gitignore.test.js +1 -1
  31. package/dist/utils/keychain.js +1 -1
  32. package/dist/utils/project.test.js +1 -1
  33. package/dist/utils/ratelimit.js +1 -1
  34. package/dist/utils/ratelimit.test.js +1 -1
  35. package/dist/utils/retry.test.js +1 -1
  36. package/dist/utils/smartContext.js +1 -1
  37. package/dist/utils/smartContext.test.js +1 -1
  38. package/dist/utils/taskPlanner.js +2 -2
  39. package/dist/utils/tools.js +5 -5
  40. package/dist/utils/tools.test.js +13 -8
  41. package/dist/utils/validation.test.js +1 -1
  42. package/dist/utils/verify.js +1 -1
  43. package/package.json +2 -1
package/bin/codeep.js ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { spawn } from 'child_process';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const main = join(__dirname, '..', 'dist', 'renderer', 'main.js');
8
+
9
+ const child = spawn(process.execPath, [main, ...process.argv.slice(2)], {
10
+ stdio: 'inherit',
11
+ env: process.env,
12
+ });
13
+
14
+ child.on('exit', (code) => process.exit(code ?? 0));
package/dist/api/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { config, getApiKey } from '../config/index';
2
- import { withRetry, isNetworkError, isTimeoutError } from '../utils/retry';
3
- import { getProvider, getProviderBaseUrl, getProviderAuthHeader } from '../config/providers';
4
- import { logApiRequest, logApiResponse } from '../utils/logger';
5
- import { loadProjectIntelligence, generateContextFromIntelligence } from '../utils/projectIntelligence';
6
- import { loadProjectRules } from '../utils/agent';
7
- import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from '../utils/tokenTracker';
1
+ import { config, getApiKey } from '../config/index.js';
2
+ import { withRetry, isNetworkError, isTimeoutError } from '../utils/retry.js';
3
+ import { getProvider, getProviderBaseUrl, getProviderAuthHeader } from '../config/providers.js';
4
+ import { logApiRequest, logApiResponse } from '../utils/logger.js';
5
+ import { loadProjectIntelligence, generateContextFromIntelligence } from '../utils/projectIntelligence.js';
6
+ import { loadProjectRules } from '../utils/agent.js';
7
+ import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from '../utils/tokenTracker.js';
8
8
  // Error messages by language
9
9
  const ERROR_MESSAGES = {
10
10
  en: {
@@ -1,8 +1,8 @@
1
1
  import Conf from 'conf';
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, statSync } from 'fs';
3
3
  import { join, dirname } from 'path';
4
- import { PROVIDERS, getProvider } from './providers';
5
- import { logSession } from '../utils/logger';
4
+ import { PROVIDERS, getProvider } from './providers.js';
5
+ import { logSession } from '../utils/logger.js';
6
6
  // We'll initialize GLOBAL_SESSIONS_DIR after config is created (to use config.path)
7
7
  /**
8
8
  * Get sessions directory - local .codeep/sessions/ if in project, otherwise global
@@ -422,7 +422,7 @@ export function getModelsForCurrentProvider() {
422
422
  return models;
423
423
  }
424
424
  // Re-export PROVIDERS for convenience
425
- export { PROVIDERS } from './providers';
425
+ export { PROVIDERS } from './providers.js';
426
426
  // Generate unique session ID
427
427
  function generateSessionId() {
428
428
  const now = new Date();
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { PROVIDERS, getProvider, getProviderList, getProviderModels, getProviderBaseUrl, getProviderAuthHeader, getProviderMcpEndpoints, } from './providers';
2
+ import { PROVIDERS, getProvider, getProviderList, getProviderModels, getProviderBaseUrl, getProviderAuthHeader, getProviderMcpEndpoints, } from './providers.js';
3
3
  describe('providers', () => {
4
4
  describe('PROVIDERS constant', () => {
5
5
  it('should have z.ai provider', () => {
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Custom hooks for Codeep
3
3
  */
4
- export { useAgent } from './useAgent';
4
+ export { useAgent } from './useAgent.js';
@@ -3,9 +3,9 @@
3
3
  * Reduces re-renders in App component when agent state changes
4
4
  */
5
5
  import { useState, useCallback, useRef } from 'react';
6
- import { runAgent, formatAgentResult } from '../utils/agent';
7
- import { createActionLog } from '../utils/tools';
8
- import { autoSaveSession } from '../config/index';
6
+ import { runAgent, formatAgentResult } from '../utils/agent.js';
7
+ import { createActionLog } from '../utils/tools.js';
8
+ import { autoSaveSession } from '../config/index.js';
9
9
  export function useAgent({ projectContext, hasWriteAccess, messages, projectPath, onMessageAdd, notify, }) {
10
10
  const [isAgentRunning, setIsAgentRunning] = useState(false);
11
11
  const [agentIteration, setAgentIteration] = useState(0);
@@ -2,9 +2,9 @@
2
2
  * Main Application using custom renderer
3
3
  * Replaces Ink-based App
4
4
  */
5
- import { Screen } from './Screen';
6
- import { Input, LineEditor } from './Input';
7
- import { fg, style, stringWidth } from './ansi';
5
+ import { Screen } from './Screen.js';
6
+ import { Input, LineEditor } from './Input.js';
7
+ import { fg, style, stringWidth } from './ansi.js';
8
8
  import clipboardy from 'clipboardy';
9
9
  import { spawn } from 'child_process';
10
10
  // Primary color: #f02a30 (Codeep red)
@@ -222,11 +222,11 @@ const COMMAND_DESCRIPTIONS = {
222
222
  'context-clear': 'Clear saved context',
223
223
  'learn': 'Learn code preferences',
224
224
  };
225
- import { helpCategories, keyboardShortcuts } from './components/Help';
226
- import { handleSettingsKey, SETTINGS } from './components/Settings';
227
- import { renderExportPanel, handleExportKey as handleExportKeyComponent } from './components/Export';
228
- import { renderLogoutPanel, handleLogoutKey as handleLogoutKeyComponent } from './components/Logout';
229
- import { renderSearchPanel, handleSearchKey as handleSearchKeyComponent } from './components/Search';
225
+ import { helpCategories, keyboardShortcuts } from './components/Help.js';
226
+ import { handleSettingsKey, SETTINGS } from './components/Settings.js';
227
+ import { renderExportPanel, handleExportKey as handleExportKeyComponent } from './components/Export.js';
228
+ import { renderLogoutPanel, handleLogoutKey as handleLogoutKeyComponent } from './components/Logout.js';
229
+ import { renderSearchPanel, handleSearchKey as handleSearchKeyComponent } from './components/Search.js';
230
230
  export class App {
231
231
  screen;
232
232
  input;
@@ -2,9 +2,9 @@
2
2
  * Simple Chat UI - Proof of Concept
3
3
  * Demonstrates custom renderer without Ink
4
4
  */
5
- import { Screen } from './Screen';
6
- import { Input, LineEditor } from './Input';
7
- import { fg } from './ansi';
5
+ import { Screen } from './Screen.js';
6
+ import { Input, LineEditor } from './Input.js';
7
+ import { fg } from './ansi.js';
8
8
  export class ChatUI {
9
9
  screen;
10
10
  input;
@@ -2,7 +2,7 @@
2
2
  * Screen buffer with diff-based rendering
3
3
  * Only writes changes to terminal - minimizes flickering
4
4
  */
5
- import { cursor, screen, style, visibleLength, charWidth } from './ansi';
5
+ import { cursor, screen, style, visibleLength, charWidth } from './ansi.js';
6
6
  export class Screen {
7
7
  width;
8
8
  height;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Export panel component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  const FORMATS = [
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Help screen component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Intro animation component - matches Ink version style
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // ASCII Logo (same as Ink version)
6
6
  const LOGO = [
7
7
  ' ██████╗ ██████╗ ██████╗ ███████╗███████╗██████╗ ',
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Login screen for API key setup
3
3
  */
4
- import { LineEditor } from '../Input';
5
- import { fg, style } from '../ansi';
6
- import { createBox, centerBox } from './Box';
4
+ import { LineEditor } from '../Input.js';
5
+ import { fg, style } from '../ansi.js';
6
+ import { createBox, centerBox } from './Box.js';
7
7
  import { spawn } from 'child_process';
8
8
  import clipboardy from 'clipboardy';
9
9
  // Primary color: #f02a30 (Codeep red)
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Logout panel component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  /**
@@ -2,8 +2,8 @@
2
2
  * Modal overlay component
3
3
  * Renders a box with content on top of existing screen
4
4
  */
5
- import { fg, style } from '../ansi';
6
- import { createBox, centerBox } from './Box';
5
+ import { fg, style } from '../ansi.js';
6
+ import { createBox, centerBox } from './Box.js';
7
7
  // Primary color: #f02a30 (Codeep red)
8
8
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
9
9
  const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Permission screen for granting folder access
3
3
  */
4
- import { fg, style } from '../ansi';
5
- import { createBox, centerBox } from './Box';
4
+ import { fg, style } from '../ansi.js';
5
+ import { createBox, centerBox } from './Box.js';
6
6
  // Primary color: #f02a30 (Codeep red)
7
7
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
8
8
  const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Search panel component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  /**
@@ -2,7 +2,7 @@
2
2
  * Generic fullscreen selection component
3
3
  * Used for Language, Provider, Model, Protocol selection
4
4
  */
5
- import { fg, style } from '../ansi';
5
+ import { fg, style } from '../ansi.js';
6
6
  // Primary color: #f02a30 (Codeep red)
7
7
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
8
8
  const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Settings screen component
3
3
  */
4
- import { fg, style } from '../ansi';
5
- import { config } from '../../config/index';
6
- import { updateRateLimits } from '../../utils/ratelimit';
4
+ import { fg, style } from '../ansi.js';
5
+ import { config } from '../../config/index.js';
6
+ import { updateRateLimits } from '../../utils/ratelimit.js';
7
7
  // Primary color: #f02a30 (Codeep red)
8
8
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
9
9
  const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Status screen component
3
3
  */
4
- import { fg, style } from '../ansi';
4
+ import { fg, style } from '../ansi.js';
5
5
  // Primary color: #f02a30 (Codeep red)
6
6
  const PRIMARY_COLOR = fg.rgb(240, 42, 48);
7
7
  /**
@@ -3,7 +3,7 @@
3
3
  * Demo for full App with modals
4
4
  * Run with: npm run demo:app
5
5
  */
6
- import { App } from './App';
6
+ import { App } from './App.js';
7
7
  // Simulate API response
8
8
  function simulateResponse(app, text) {
9
9
  return new Promise((resolve) => {
@@ -3,7 +3,7 @@
3
3
  * Demo/Test for custom renderer
4
4
  * Run with: npx ts-node src/renderer/demo.ts
5
5
  */
6
- import { ChatUI } from './ChatUI';
6
+ import { ChatUI } from './ChatUI.js';
7
7
  // Simulate streaming response
8
8
  function simulateStreaming(ui, text) {
9
9
  return new Promise((resolve) => {
@@ -5,13 +5,13 @@
5
5
  * Uses direct ANSI escape codes and a virtual screen buffer
6
6
  * with diff-based rendering for flicker-free updates.
7
7
  */
8
- export { cursor, screen, fg, bg, style, styled, stripAnsi, visibleLength, truncate, wordWrap } from './ansi';
9
- export { Screen } from './Screen';
10
- export { Input, LineEditor } from './Input';
11
- export { ChatUI } from './ChatUI';
12
- export { App } from './App';
8
+ export { cursor, screen, fg, bg, style, styled, stripAnsi, visibleLength, truncate, wordWrap } from './ansi.js';
9
+ export { Screen } from './Screen.js';
10
+ export { Input, LineEditor } from './Input.js';
11
+ export { ChatUI } from './ChatUI.js';
12
+ export { App } from './App.js';
13
13
  // Components
14
- export { createBox, centerBox } from './components/Box';
15
- export { renderModal, renderHelpModal, renderListModal } from './components/Modal';
16
- export { renderHelpScreen, helpCategories, keyboardShortcuts } from './components/Help';
17
- export { renderStatusScreen } from './components/Status';
14
+ export { createBox, centerBox } from './components/Box.js';
15
+ export { renderModal, renderHelpModal, renderListModal } from './components/Modal.js';
16
+ export { renderHelpScreen, helpCategories, keyboardShortcuts } from './components/Help.js';
17
+ export { renderStatusScreen } from './components/Status.js';
@@ -3,19 +3,19 @@
3
3
  * Codeep with Custom Renderer
4
4
  * Main entry point using the new ANSI-based renderer instead of Ink
5
5
  */
6
- import { App } from './App';
7
- import { Screen } from './Screen';
8
- import { Input } from './Input';
9
- import { LoginScreen, renderProviderSelect } from './components/Login';
10
- import { renderPermissionScreen, getPermissionOptions } from './components/Permission';
6
+ import { App } from './App.js';
7
+ import { Screen } from './Screen.js';
8
+ import { Input } from './Input.js';
9
+ import { LoginScreen, renderProviderSelect } from './components/Login.js';
10
+ import { renderPermissionScreen, getPermissionOptions } from './components/Permission.js';
11
11
  // Intro animation is now handled by App.startIntro()
12
- import { chat, setProjectContext } from '../api/index';
13
- import { runAgent } from '../utils/agent';
14
- import { config, loadApiKey, loadAllApiKeys, getCurrentProvider, getModelsForCurrentProvider, PROTOCOLS, LANGUAGES, setProvider, setApiKey, clearApiKey, getApiKey, autoSaveSession, saveSession, startNewSession, getCurrentSessionId, loadSession, listSessionsWithInfo, deleteSession, renameSession, hasReadPermission, hasWritePermission, setProjectPermission, initializeAsProject, isManuallyInitializedProject, } from '../config/index';
15
- import { isProjectDirectory, getProjectContext } from '../utils/project';
16
- import { getCurrentVersion } from '../utils/update';
17
- import { getProviderList, getProvider } from '../config/providers';
18
- import { getSessionStats } from '../utils/tokenTracker';
12
+ import { chat, setProjectContext } from '../api/index.js';
13
+ import { runAgent } from '../utils/agent.js';
14
+ import { config, loadApiKey, loadAllApiKeys, getCurrentProvider, getModelsForCurrentProvider, PROTOCOLS, LANGUAGES, setProvider, setApiKey, clearApiKey, getApiKey, autoSaveSession, saveSession, startNewSession, getCurrentSessionId, loadSession, listSessionsWithInfo, deleteSession, renameSession, hasReadPermission, hasWritePermission, setProjectPermission, initializeAsProject, isManuallyInitializedProject, } from '../config/index.js';
15
+ import { isProjectDirectory, getProjectContext } from '../utils/project.js';
16
+ import { getCurrentVersion } from '../utils/update.js';
17
+ import { getProviderList, getProvider } from '../config/providers.js';
18
+ import { getSessionStats } from '../utils/tokenTracker.js';
19
19
  // State
20
20
  let projectPath = process.cwd();
21
21
  let projectContext = null;
@@ -67,7 +67,7 @@ function formatAddedFilesContext() {
67
67
  async function handleSubmit(message) {
68
68
  // Check if we're waiting for interactive mode answers
69
69
  if (pendingInteractiveContext) {
70
- const { parseAnswers, enhancePromptWithAnswers } = await import('../utils/interactive');
70
+ const { parseAnswers, enhancePromptWithAnswers } = await import('../utils/interactive.js');
71
71
  const answers = parseAnswers(message, pendingInteractiveContext.context);
72
72
  // Enhance the original prompt with user's answers
73
73
  const enhancedTask = enhancePromptWithAnswers(pendingInteractiveContext.context, answers);
@@ -213,7 +213,7 @@ async function runAgentTask(task, dryRun = false) {
213
213
  const interactiveMode = config.get('agentInteractive') !== false;
214
214
  if (interactiveMode) {
215
215
  // Analyze task for ambiguity
216
- const { analyzeForClarification, formatQuestions } = await import('../utils/interactive');
216
+ const { analyzeForClarification, formatQuestions } = await import('../utils/interactive.js');
217
217
  const interactiveContext = analyzeForClarification(task);
218
218
  if (interactiveContext.needsClarification) {
219
219
  // Store context for follow-up
@@ -429,7 +429,7 @@ async function executeAgentTask(task, dryRun = false) {
429
429
  // Auto-commit if enabled and there were file changes
430
430
  if (!dryRun && config.get('agentAutoCommit') && result.actions.length > 0) {
431
431
  try {
432
- const { autoCommitAgentChanges, createBranchAndCommit } = await import('../utils/git');
432
+ const { autoCommitAgentChanges, createBranchAndCommit } = await import('../utils/git.js');
433
433
  const useBranch = config.get('agentAutoCommitBranch');
434
434
  if (useBranch) {
435
435
  const commitResult = createBranchAndCommit(task, result.actions, context.root);
@@ -482,13 +482,13 @@ async function executeAgentTask(task, dryRun = false) {
482
482
  * Wires the skill execution engine to App's UI.
483
483
  */
484
484
  async function runSkill(nameOrShortcut, args) {
485
- const { findSkill, parseSkillArgs, executeSkill, trackSkillUsage } = await import('../utils/skills');
485
+ const { findSkill, parseSkillArgs, executeSkill, trackSkillUsage } = await import('../utils/skills.js');
486
486
  const skill = findSkill(nameOrShortcut);
487
487
  if (!skill)
488
488
  return false;
489
489
  // Pre-flight checks
490
490
  if (skill.requiresGit) {
491
- const { getGitStatus } = await import('../utils/git');
491
+ const { getGitStatus } = await import('../utils/git.js');
492
492
  if (!projectPath || !getGitStatus(projectPath).isRepo) {
493
493
  app.notify('This skill requires a git repository');
494
494
  return true;
@@ -732,7 +732,7 @@ function handleCommand(command, args) {
732
732
  const staged = args.includes('--staged') || args.includes('-s');
733
733
  app.notify(staged ? 'Getting staged diff...' : 'Getting diff...');
734
734
  // Import dynamically to avoid circular deps
735
- import('../utils/git').then(({ getGitDiff, formatDiffForDisplay }) => {
735
+ import('../utils/git.js').then(({ getGitDiff, formatDiffForDisplay }) => {
736
736
  const result = getGitDiff(staged, projectPath);
737
737
  if (!result.success || !result.diff) {
738
738
  app.notify(result.error || 'No changes');
@@ -746,14 +746,14 @@ function handleCommand(command, args) {
746
746
  break;
747
747
  }
748
748
  case 'undo': {
749
- import('../utils/agent').then(({ undoLastAction }) => {
749
+ import('../utils/agent.js').then(({ undoLastAction }) => {
750
750
  const result = undoLastAction();
751
751
  app.notify(result.success ? `Undo: ${result.message}` : `Cannot undo: ${result.message}`);
752
752
  });
753
753
  break;
754
754
  }
755
755
  case 'undo-all': {
756
- import('../utils/agent').then(({ undoAllActions }) => {
756
+ import('../utils/agent.js').then(({ undoAllActions }) => {
757
757
  const result = undoAllActions();
758
758
  app.notify(result.success ? `Undone ${result.results.length} action(s)` : 'Nothing to undo');
759
759
  });
@@ -765,7 +765,7 @@ function handleCommand(command, args) {
765
765
  return;
766
766
  }
767
767
  app.notify('Scanning project...');
768
- import('../utils/projectIntelligence').then(({ scanProject, saveProjectIntelligence, generateContextFromIntelligence }) => {
768
+ import('../utils/projectIntelligence.js').then(({ scanProject, saveProjectIntelligence, generateContextFromIntelligence }) => {
769
769
  scanProject(projectContext.root).then(intelligence => {
770
770
  saveProjectIntelligence(projectContext.root, intelligence);
771
771
  const context = generateContextFromIntelligence(intelligence);
@@ -785,7 +785,7 @@ function handleCommand(command, args) {
785
785
  app.notify('No project context');
786
786
  return;
787
787
  }
788
- import('../utils/codeReview').then(({ performCodeReview, formatReviewResult }) => {
788
+ import('../utils/codeReview.js').then(({ performCodeReview, formatReviewResult }) => {
789
789
  const reviewFiles = args.length > 0 ? args : undefined;
790
790
  const result = performCodeReview(projectContext, reviewFiles);
791
791
  app.addMessage({
@@ -797,7 +797,7 @@ function handleCommand(command, args) {
797
797
  }
798
798
  case 'update': {
799
799
  app.notify('Checking for updates...');
800
- import('../utils/update').then(({ checkForUpdates, formatVersionInfo }) => {
800
+ import('../utils/update.js').then(({ checkForUpdates, formatVersionInfo }) => {
801
801
  checkForUpdates().then(info => {
802
802
  const message = formatVersionInfo(info);
803
803
  app.notify(message.split('\n')[0], 5000);
@@ -1017,7 +1017,7 @@ function handleCommand(command, args) {
1017
1017
  app.notify(`Invalid block number. Available: 1-${codeBlocks.length}`);
1018
1018
  return;
1019
1019
  }
1020
- import('../utils/clipboard').then(({ copyToClipboard }) => {
1020
+ import('../utils/clipboard.js').then(({ copyToClipboard }) => {
1021
1021
  if (copyToClipboard(codeBlocks[index])) {
1022
1022
  app.notify(`Copied block ${index + 1} to clipboard`);
1023
1023
  }
@@ -1219,7 +1219,7 @@ function handleCommand(command, args) {
1219
1219
  }
1220
1220
  // Agent history and changes
1221
1221
  case 'history': {
1222
- import('../utils/agent').then(({ getAgentHistory }) => {
1222
+ import('../utils/agent.js').then(({ getAgentHistory }) => {
1223
1223
  const history = getAgentHistory();
1224
1224
  if (history.length === 0) {
1225
1225
  app.notify('No agent history');
@@ -1239,7 +1239,7 @@ function handleCommand(command, args) {
1239
1239
  break;
1240
1240
  }
1241
1241
  case 'changes': {
1242
- import('../utils/agent').then(({ getCurrentSessionActions }) => {
1242
+ import('../utils/agent.js').then(({ getCurrentSessionActions }) => {
1243
1243
  const actions = getCurrentSessionActions();
1244
1244
  if (actions.length === 0) {
1245
1245
  app.notify('No changes in current session');
@@ -1286,7 +1286,7 @@ function handleCommand(command, args) {
1286
1286
  // Learning mode
1287
1287
  case 'learn': {
1288
1288
  if (args[0] === 'status') {
1289
- import('../utils/learning').then(({ getLearningStatus }) => {
1289
+ import('../utils/learning.js').then(({ getLearningStatus }) => {
1290
1290
  const status = getLearningStatus(projectPath);
1291
1291
  app.addMessage({
1292
1292
  role: 'system',
@@ -1298,7 +1298,7 @@ function handleCommand(command, args) {
1298
1298
  return;
1299
1299
  }
1300
1300
  if (args[0] === 'rule' && args.length > 1) {
1301
- import('../utils/learning').then(({ addCustomRule }) => {
1301
+ import('../utils/learning.js').then(({ addCustomRule }) => {
1302
1302
  addCustomRule(projectPath, args.slice(1).join(' '));
1303
1303
  app.notify('Custom rule added');
1304
1304
  }).catch(() => {
@@ -1311,7 +1311,7 @@ function handleCommand(command, args) {
1311
1311
  return;
1312
1312
  }
1313
1313
  app.notify('Learning from project...');
1314
- import('../utils/learning').then(({ learnFromProject, formatPreferencesForPrompt }) => {
1314
+ import('../utils/learning.js').then(({ learnFromProject, formatPreferencesForPrompt }) => {
1315
1315
  // Get some source files to learn from
1316
1316
  import('fs').then(fs => {
1317
1317
  import('path').then(path => {
@@ -1394,7 +1394,7 @@ function handleCommand(command, args) {
1394
1394
  break;
1395
1395
  }
1396
1396
  case 'skills': {
1397
- import('../utils/skills').then(({ getAllSkills, searchSkills, formatSkillsList, getSkillStats }) => {
1397
+ import('../utils/skills.js').then(({ getAllSkills, searchSkills, formatSkillsList, getSkillStats }) => {
1398
1398
  const query = args.join(' ').toLowerCase();
1399
1399
  // Check for stats subcommand
1400
1400
  if (query === 'stats') {
@@ -1418,7 +1418,7 @@ function handleCommand(command, args) {
1418
1418
  break;
1419
1419
  }
1420
1420
  case 'skill': {
1421
- import('../utils/skills').then(({ findSkill, formatSkillHelp, createSkillTemplate, saveCustomSkill, deleteCustomSkill }) => {
1421
+ import('../utils/skills.js').then(({ findSkill, formatSkillHelp, createSkillTemplate, saveCustomSkill, deleteCustomSkill }) => {
1422
1422
  const subCommand = args[0]?.toLowerCase();
1423
1423
  const skillName = args[1];
1424
1424
  if (!subCommand) {
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { existsSync, readFileSync } from 'fs';
5
5
  import { join } from 'path';
6
- import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from './tokenTracker';
6
+ import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from './tokenTracker.js';
7
7
  // Debug logging helper - only logs when CODEEP_DEBUG=1
8
8
  const debug = (...args) => {
9
9
  if (process.env.CODEEP_DEBUG === '1') {
@@ -38,13 +38,13 @@ function calculateDynamicTimeout(prompt, iteration, baseTimeout) {
38
38
  const calculatedTimeout = baseTimeout * multiplier;
39
39
  return Math.min(Math.max(calculatedTimeout, 120000), 300000);
40
40
  }
41
- import { parseToolCalls, executeTool, createActionLog, formatToolDefinitions, getOpenAITools, getAnthropicTools, parseOpenAIToolCalls, parseAnthropicToolCalls } from './tools';
42
- import { config, getApiKey } from '../config/index';
43
- import { getProviderBaseUrl, getProviderAuthHeader, supportsNativeTools } from '../config/providers';
44
- import { startSession, endSession, undoLastAction, undoAllActions, getCurrentSession, getRecentSessions, formatSession } from './history';
45
- import { runAllVerifications, formatErrorsForAgent, hasVerificationErrors, getVerificationSummary } from './verify';
46
- import { gatherSmartContext, formatSmartContext, extractTargetFile } from './smartContext';
47
- import { planTasks, formatTaskPlan } from './taskPlanner';
41
+ import { parseToolCalls, executeTool, createActionLog, formatToolDefinitions, getOpenAITools, getAnthropicTools, parseOpenAIToolCalls, parseAnthropicToolCalls } from './tools.js';
42
+ import { config, getApiKey } from '../config/index.js';
43
+ import { getProviderBaseUrl, getProviderAuthHeader, supportsNativeTools } from '../config/providers.js';
44
+ import { startSession, endSession, undoLastAction, undoAllActions, getCurrentSession, getRecentSessions, formatSession } from './history.js';
45
+ import { runAllVerifications, formatErrorsForAgent, hasVerificationErrors, getVerificationSummary } from './verify.js';
46
+ import { gatherSmartContext, formatSmartContext, extractTargetFile } from './smartContext.js';
47
+ import { planTasks, formatTaskPlan } from './taskPlanner.js';
48
48
  const DEFAULT_OPTIONS = {
49
49
  maxIterations: 100, // Increased for large tasks
50
50
  maxDuration: 20 * 60 * 1000, // 20 minutes
@@ -11,7 +11,7 @@ vi.mock('fs', async (importOriginal) => {
11
11
  });
12
12
  import { existsSync, readFileSync } from 'fs';
13
13
  import { join } from 'path';
14
- import { loadProjectRules, formatAgentResult } from './agent';
14
+ import { loadProjectRules, formatAgentResult } from './agent.js';
15
15
  // Cast mocked functions for convenience
16
16
  const mockExistsSync = existsSync;
17
17
  const mockReadFileSync = readFileSync;
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { existsSync, readFileSync, readdirSync } from 'fs';
5
5
  import { join, extname, relative } from 'path';
6
- import { getChangedFiles } from './git';
6
+ import { getChangedFiles } from './git.js';
7
7
  // Common code patterns that indicate issues
8
8
  const CODE_PATTERNS = [
9
9
  // Security issues
@@ -4,7 +4,7 @@
4
4
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
5
5
  import { join, basename } from 'path';
6
6
  import { homedir } from 'os';
7
- import { logger } from './logger';
7
+ import { logger } from './logger.js';
8
8
  // Context storage directory
9
9
  const CONTEXT_DIR = join(homedir(), '.codeep', 'contexts');
10
10
  /**
@@ -3,7 +3,7 @@ import { execSync } from 'child_process';
3
3
  import { mkdirSync, rmSync, writeFileSync } from 'fs';
4
4
  import { join } from 'path';
5
5
  import { tmpdir } from 'os';
6
- import { isGitRepository, getGitStatus, getGitDiff, getChangedFiles, suggestCommitMessage, createCommit, stageAll, formatDiffForDisplay, } from './git';
6
+ import { isGitRepository, getGitStatus, getGitDiff, getChangedFiles, suggestCommitMessage, createCommit, stageAll, formatDiffForDisplay, } from './git.js';
7
7
  // Create a temp directory for git tests
8
8
  const TEST_DIR = join(tmpdir(), 'codeep-git-test-' + Date.now());
9
9
  const NON_GIT_DIR = join(tmpdir(), 'codeep-non-git-test-' + Date.now());
@@ -5,7 +5,7 @@ vi.mock('fs', () => ({
5
5
  readFileSync: vi.fn(),
6
6
  }));
7
7
  import { existsSync, readFileSync } from 'fs';
8
- import { loadIgnoreRules, isIgnored } from './gitignore';
8
+ import { loadIgnoreRules, isIgnored } from './gitignore.js';
9
9
  const mockExistsSync = existsSync;
10
10
  const mockReadFileSync = readFileSync;
11
11
  beforeEach(() => {
@@ -1,5 +1,5 @@
1
1
  import keytar from 'keytar';
2
- import { logger } from './logger';
2
+ import { logger } from './logger.js';
3
3
  const SERVICE_NAME = 'codeep';
4
4
  class KeychainStorage {
5
5
  getAccountName(providerId) {
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import { mkdirSync, rmSync, writeFileSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { tmpdir } from 'os';
5
- import { isProjectDirectory, getProjectType, scanDirectory, generateTreeStructure, readProjectFile, deleteProjectFile, writeProjectFile, } from './project';
5
+ import { isProjectDirectory, getProjectType, scanDirectory, generateTreeStructure, readProjectFile, deleteProjectFile, writeProjectFile, } from './project.js';
6
6
  const TEST_DIR = join(tmpdir(), 'codeep-project-test-' + Date.now());
7
7
  describe('project utilities', () => {
8
8
  beforeEach(() => {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Rate limiting utility to prevent API abuse
3
3
  */
4
- import { config } from '../config/index';
4
+ import { config } from '../config/index.js';
5
5
  class RateLimiter {
6
6
  requests = [];
7
7
  config;
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { checkApiRateLimit, checkCommandRateLimit, resetRateLimits, getRateLimitStatus, } from './ratelimit';
2
+ import { checkApiRateLimit, checkCommandRateLimit, resetRateLimits, getRateLimitStatus, } from './ratelimit.js';
3
3
  describe('ratelimit utilities', () => {
4
4
  beforeEach(() => {
5
5
  // Reset rate limiters before each test
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
- import { withRetry, isNetworkError, isTimeoutError, fetchWithTimeout, } from './retry';
2
+ import { withRetry, isNetworkError, isTimeoutError, fetchWithTimeout, } from './retry.js';
3
3
  describe('retry utilities', () => {
4
4
  describe('isNetworkError', () => {
5
5
  it('should detect fetch TypeError', () => {
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { existsSync, readFileSync, statSync } from 'fs';
5
5
  import { join, dirname, basename, extname, relative } from 'path';
6
- import { loadIgnoreRules, isIgnored } from './gitignore';
6
+ import { loadIgnoreRules, isIgnored } from './gitignore.js';
7
7
  // Max context size (characters)
8
8
  const MAX_CONTEXT_SIZE = 50000;
9
9
  const MAX_FILES = 15;
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { extractTargetFile, formatSmartContext, } from './smartContext';
2
+ import { extractTargetFile, formatSmartContext, } from './smartContext.js';
3
3
  // ---------------------------------------------------------------------------
4
4
  // Helper to build a SmartContextResult quickly
5
5
  // ---------------------------------------------------------------------------
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Task Planning - breaks down complex tasks into subtasks
3
3
  */
4
- import { config, getApiKey } from '../config/index';
5
- import { getProviderBaseUrl, getProviderAuthHeader } from '../config/providers';
4
+ import { config, getApiKey } from '../config/index.js';
5
+ import { getProviderBaseUrl, getProviderAuthHeader } from '../config/providers.js';
6
6
  /**
7
7
  * Ask AI to break down a complex task into subtasks
8
8
  */
@@ -9,11 +9,11 @@ const debug = (...args) => {
9
9
  }
10
10
  };
11
11
  import { join, dirname, relative, resolve, isAbsolute } from 'path';
12
- import { executeCommand } from './shell';
13
- import { recordWrite, recordEdit, recordDelete, recordMkdir, recordCommand } from './history';
14
- import { loadIgnoreRules, isIgnored } from './gitignore';
15
- import { config, getApiKey } from '../config/index';
16
- import { getProviderMcpEndpoints } from '../config/providers';
12
+ import { executeCommand } from './shell.js';
13
+ import { recordWrite, recordEdit, recordDelete, recordMkdir, recordCommand } from './history.js';
14
+ import { loadIgnoreRules, isIgnored } from './gitignore.js';
15
+ import { config, getApiKey } from '../config/index.js';
16
+ import { getProviderMcpEndpoints } from '../config/providers.js';
17
17
  // Z.AI MCP tool names (available when user has any Z.AI API key)
18
18
  const ZAI_MCP_TOOLS = ['web_search', 'web_read', 'github_read'];
19
19
  // Z.AI provider IDs that have MCP endpoints
@@ -1,12 +1,16 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { getOpenAITools, getAnthropicTools, parseToolCalls, parseOpenAIToolCalls, parseAnthropicToolCalls, createActionLog, AGENT_TOOLS, } from './tools';
2
+ import { getOpenAITools, getAnthropicTools, parseToolCalls, parseOpenAIToolCalls, parseAnthropicToolCalls, createActionLog, AGENT_TOOLS, } from './tools.js';
3
3
  // ─── CONSTANTS ───────────────────────────────────────────────────────────────
4
4
  const ALL_TOOL_NAMES = Object.keys(AGENT_TOOLS);
5
+ // MCP tools are filtered out when no Z.AI API key is configured (e.g. in tests)
6
+ const ZAI_MCP_TOOLS = ['web_search', 'web_read', 'github_read'];
7
+ const CORE_TOOL_NAMES = ALL_TOOL_NAMES.filter(n => !ZAI_MCP_TOOLS.includes(n));
5
8
  // ─── getOpenAITools ──────────────────────────────────────────────────────────
6
9
  describe('getOpenAITools', () => {
7
10
  it('should return one entry per AGENT_TOOLS definition', () => {
8
11
  const tools = getOpenAITools();
9
- expect(tools).toHaveLength(ALL_TOOL_NAMES.length);
12
+ // MCP tools are excluded when no Z.AI API key is configured
13
+ expect(tools).toHaveLength(CORE_TOOL_NAMES.length);
10
14
  });
11
15
  it('should wrap every tool in the OpenAI function-calling envelope', () => {
12
16
  const tools = getOpenAITools();
@@ -20,10 +24,10 @@ describe('getOpenAITools', () => {
20
24
  expect(Array.isArray(tool.function.parameters.required)).toBe(true);
21
25
  }
22
26
  });
23
- it('should include all tool names from AGENT_TOOLS', () => {
27
+ it('should include all core tool names from AGENT_TOOLS', () => {
24
28
  const tools = getOpenAITools();
25
29
  const names = tools.map(t => t.function.name);
26
- for (const name of ALL_TOOL_NAMES) {
30
+ for (const name of CORE_TOOL_NAMES) {
27
31
  expect(names).toContain(name);
28
32
  }
29
33
  });
@@ -71,7 +75,8 @@ describe('getOpenAITools', () => {
71
75
  describe('getAnthropicTools', () => {
72
76
  it('should return one entry per AGENT_TOOLS definition', () => {
73
77
  const tools = getAnthropicTools();
74
- expect(tools).toHaveLength(ALL_TOOL_NAMES.length);
78
+ // MCP tools are excluded when no Z.AI API key is configured
79
+ expect(tools).toHaveLength(CORE_TOOL_NAMES.length);
75
80
  });
76
81
  it('should use Anthropic tool-use shape (name, description, input_schema)', () => {
77
82
  const tools = getAnthropicTools();
@@ -84,17 +89,17 @@ describe('getAnthropicTools', () => {
84
89
  expect(Array.isArray(tool.input_schema.required)).toBe(true);
85
90
  }
86
91
  });
87
- it('should include all tool names from AGENT_TOOLS', () => {
92
+ it('should include all core tool names from AGENT_TOOLS', () => {
88
93
  const tools = getAnthropicTools();
89
94
  const names = tools.map(t => t.name);
90
- for (const name of ALL_TOOL_NAMES) {
95
+ for (const name of CORE_TOOL_NAMES) {
91
96
  expect(names).toContain(name);
92
97
  }
93
98
  });
94
99
  it('should have same required params as OpenAI format', () => {
95
100
  const openai = getOpenAITools();
96
101
  const anthropic = getAnthropicTools();
97
- for (const name of ALL_TOOL_NAMES) {
102
+ for (const name of CORE_TOOL_NAMES) {
98
103
  const oTool = openai.find(t => t.function.name === name);
99
104
  const aTool = anthropic.find(t => t.name === name);
100
105
  expect(aTool.input_schema.required).toEqual(oTool.function.parameters.required);
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { validateInput, validateApiKey, validateCommandArgs, validateFilePath, sanitizeOutput, } from './validation';
2
+ import { validateInput, validateApiKey, validateCommandArgs, validateFilePath, sanitizeOutput, } from './validation.js';
3
3
  describe('validation utilities', () => {
4
4
  describe('validateInput', () => {
5
5
  it('should reject empty input', () => {
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { existsSync, readFileSync } from 'fs';
6
6
  import { join } from 'path';
7
- import { executeCommand } from './shell';
7
+ import { executeCommand } from './shell.js';
8
8
  const DEFAULT_OPTIONS = {
9
9
  runBuild: true,
10
10
  runTest: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.2.12",
3
+ "version": "1.2.13",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "dev": "node --import tsx src/renderer/main.ts",
12
+ "prepack": "tsc; node scripts/fix-imports.js",
12
13
  "build": "tsc && node scripts/fix-imports.js",
13
14
  "start": "node dist/renderer/main.js",
14
15
  "demo:renderer": "node --import tsx src/renderer/demo.ts",