codeep 1.3.42 → 2.0.1

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 (60) hide show
  1. package/README.md +208 -0
  2. package/dist/acp/commands.js +770 -7
  3. package/dist/acp/protocol.d.ts +11 -2
  4. package/dist/acp/server.js +179 -11
  5. package/dist/acp/session.d.ts +3 -0
  6. package/dist/acp/session.js +5 -0
  7. package/dist/api/index.js +39 -6
  8. package/dist/config/index.d.ts +13 -0
  9. package/dist/config/index.js +45 -0
  10. package/dist/config/providers.js +76 -1
  11. package/dist/renderer/App.d.ts +12 -0
  12. package/dist/renderer/App.js +109 -4
  13. package/dist/renderer/agentExecution.js +5 -0
  14. package/dist/renderer/commands.js +638 -2
  15. package/dist/renderer/components/Help.js +28 -0
  16. package/dist/renderer/components/Login.d.ts +1 -0
  17. package/dist/renderer/components/Login.js +24 -9
  18. package/dist/renderer/handlers.d.ts +11 -1
  19. package/dist/renderer/handlers.js +30 -0
  20. package/dist/renderer/main.js +73 -0
  21. package/dist/utils/agent.d.ts +17 -0
  22. package/dist/utils/agent.js +91 -7
  23. package/dist/utils/agentChat.d.ts +10 -2
  24. package/dist/utils/agentChat.js +48 -9
  25. package/dist/utils/agentStream.js +6 -2
  26. package/dist/utils/checkpoints.d.ts +93 -0
  27. package/dist/utils/checkpoints.js +205 -0
  28. package/dist/utils/context.d.ts +24 -0
  29. package/dist/utils/context.js +57 -0
  30. package/dist/utils/customCommands.d.ts +62 -0
  31. package/dist/utils/customCommands.js +201 -0
  32. package/dist/utils/hooks.d.ts +97 -0
  33. package/dist/utils/hooks.js +223 -0
  34. package/dist/utils/mcpClient.d.ts +229 -0
  35. package/dist/utils/mcpClient.js +497 -0
  36. package/dist/utils/mcpConfig.d.ts +55 -0
  37. package/dist/utils/mcpConfig.js +177 -0
  38. package/dist/utils/mcpMarketplace.d.ts +49 -0
  39. package/dist/utils/mcpMarketplace.js +175 -0
  40. package/dist/utils/mcpRegistry.d.ts +129 -0
  41. package/dist/utils/mcpRegistry.js +427 -0
  42. package/dist/utils/mcpSamplingBridge.d.ts +32 -0
  43. package/dist/utils/mcpSamplingBridge.js +88 -0
  44. package/dist/utils/mcpStreamableHttp.d.ts +65 -0
  45. package/dist/utils/mcpStreamableHttp.js +207 -0
  46. package/dist/utils/openrouterPrefs.d.ts +36 -0
  47. package/dist/utils/openrouterPrefs.js +83 -0
  48. package/dist/utils/skillBundles.d.ts +84 -0
  49. package/dist/utils/skillBundles.js +257 -0
  50. package/dist/utils/skillBundlesCloud.d.ts +69 -0
  51. package/dist/utils/skillBundlesCloud.js +202 -0
  52. package/dist/utils/tokenTracker.d.ts +14 -2
  53. package/dist/utils/tokenTracker.js +59 -41
  54. package/dist/utils/toolExecution.d.ts +17 -1
  55. package/dist/utils/toolExecution.js +184 -6
  56. package/dist/utils/tools.d.ts +22 -6
  57. package/dist/utils/tools.js +83 -8
  58. package/package.json +3 -2
  59. package/bin/codeep-macos-arm64 +0 -0
  60. package/bin/codeep-macos-x64 +0 -0
@@ -275,6 +275,42 @@ export const PROVIDERS = {
275
275
  groupLabel: 'Google AI',
276
276
  hint: 'Pay-per-use via Google AI API key (aistudio.google.com).',
277
277
  },
278
+ 'openrouter': {
279
+ name: 'OpenRouter',
280
+ description: 'Unified access to 100+ models via one API key',
281
+ protocols: {
282
+ openai: {
283
+ baseUrl: 'https://openrouter.ai/api/v1',
284
+ authHeader: 'Bearer',
285
+ supportsNativeTools: true,
286
+ },
287
+ },
288
+ // Top 12 — the full catalog (100+) is fetched lazily via
289
+ // fetchOpenRouterModels() because dynamicModels is true.
290
+ // We keep these hardcoded so first-time users without network
291
+ // get a working dropdown.
292
+ models: [
293
+ { id: 'openrouter/auto', name: 'Auto-route', description: 'OpenRouter picks the best model for the task' },
294
+ { id: 'anthropic/claude-opus-4', name: 'Claude Opus 4', description: 'Anthropic — most capable' },
295
+ { id: 'anthropic/claude-sonnet-4', name: 'Claude Sonnet 4', description: 'Anthropic — balanced' },
296
+ { id: 'openai/gpt-5.5', name: 'GPT-5.5', description: 'OpenAI — flagship' },
297
+ { id: 'openai/gpt-5.4-mini', name: 'GPT-5.4 Mini', description: 'OpenAI — fast/cheap' },
298
+ { id: 'google/gemini-3.1-pro', name: 'Gemini 3.1 Pro', description: 'Google — multimodal' },
299
+ { id: 'meta-llama/llama-3.1-405b-instruct', name: 'Llama 3.1 405B', description: 'Meta — open weights, largest' },
300
+ { id: 'meta-llama/llama-3.1-70b-instruct', name: 'Llama 3.1 70B', description: 'Meta — open weights, fast' },
301
+ { id: 'deepseek/deepseek-v4', name: 'DeepSeek V4', description: 'DeepSeek via OpenRouter' },
302
+ { id: 'mistralai/mistral-large', name: 'Mistral Large', description: 'Mistral — flagship' },
303
+ { id: 'qwen/qwen-2.5-coder-32b-instruct', name: 'Qwen 2.5 Coder 32B', description: 'Alibaba — coding-tuned' },
304
+ { id: 'x-ai/grok-2', name: 'Grok 2', description: 'xAI via OpenRouter' },
305
+ ],
306
+ defaultModel: 'anthropic/claude-opus-4',
307
+ defaultProtocol: 'openai',
308
+ envKey: 'OPENROUTER_API_KEY',
309
+ subscribeUrl: 'https://openrouter.ai/keys',
310
+ dynamicModels: true,
311
+ groupLabel: 'OpenRouter — Aggregator',
312
+ hint: 'One key for 100+ models. Pay-per-use via openrouter.ai.',
313
+ },
278
314
  'ollama': {
279
315
  name: 'Ollama (local)',
280
316
  description: 'Run models locally with Ollama',
@@ -299,8 +335,47 @@ export const PROVIDERS = {
299
335
  export function getProvider(id) {
300
336
  return PROVIDERS[id] || null;
301
337
  }
338
+ /**
339
+ * Curated display order for the first-run login flow + `/provider` /
340
+ * `/login` pickers. Headline / popular providers float to the top so
341
+ * brand-new users see them first; regional + parameter-variant entries
342
+ * (Z.AI China, MiniMax variants) trail. Any provider id not in this
343
+ * list is appended afterward in object-declaration order, so adding a
344
+ * new provider to PROVIDERS without touching this list still shows up.
345
+ */
346
+ const DISPLAY_ORDER = [
347
+ 'anthropic',
348
+ 'openai',
349
+ 'openrouter', // 100+ models, one key — surfaced high on purpose for 2.0.0.
350
+ 'z.ai',
351
+ 'z.ai-api',
352
+ 'deepseek',
353
+ 'google',
354
+ 'minimax',
355
+ 'minimax-api',
356
+ 'ollama',
357
+ 'z.ai-cn',
358
+ 'z.ai-cn-api',
359
+ 'minimax-cn',
360
+ ];
302
361
  export function getProviderList() {
303
- return Object.entries(PROVIDERS).map(([id, config]) => ({
362
+ const all = Object.entries(PROVIDERS);
363
+ const byId = new Map(all);
364
+ const ordered = [];
365
+ // 1) Curated order first.
366
+ for (const id of DISPLAY_ORDER) {
367
+ const cfg = byId.get(id);
368
+ if (cfg) {
369
+ ordered.push([id, cfg]);
370
+ byId.delete(id);
371
+ }
372
+ }
373
+ // 2) Anything else, in declaration order.
374
+ for (const entry of all) {
375
+ if (byId.has(entry[0]))
376
+ ordered.push(entry);
377
+ }
378
+ return ordered.map(([id, config]) => ({
304
379
  id,
305
380
  name: config.name,
306
381
  description: config.description,
@@ -69,7 +69,11 @@ export declare class App {
69
69
  private confirmSelection;
70
70
  private menuOpen;
71
71
  private menuTitle;
72
+ /** Filtered view shown to the user; derived from `menuItemsAll` + `menuFilter`. */
72
73
  private menuItems;
74
+ /** Full unfiltered list captured on `showSelect`. */
75
+ private menuItemsAll;
76
+ private menuFilter;
73
77
  private menuIndex;
74
78
  private menuCurrentValue;
75
79
  private menuCallback;
@@ -271,6 +275,7 @@ export declare class App {
271
275
  showLogin(providers: Array<{
272
276
  id: string;
273
277
  name: string;
278
+ description?: string;
274
279
  subscribeUrl?: string;
275
280
  }>, callback: (result: {
276
281
  providerId: string;
@@ -284,6 +289,13 @@ export declare class App {
284
289
  * Show inline menu (renders below status bar)
285
290
  */
286
291
  showSelect(title: string, items: SelectItem[], currentValue: string, callback: (item: SelectItem) => void): void;
292
+ /**
293
+ * Recompute the visible menu list from the current filter string.
294
+ * Matches across key/label/description, case-insensitive. Keeps the
295
+ * current value highlighted if it survives the filter; otherwise
296
+ * cursor returns to the top.
297
+ */
298
+ private applyMenuFilter;
287
299
  /**
288
300
  * Handle keyboard input
289
301
  */
@@ -82,6 +82,15 @@ const COMMAND_DESCRIPTIONS = {
82
82
  'profile': 'Save/load settings profiles',
83
83
  'tasks': 'Show pending tasks from codeep.dev dashboard',
84
84
  'sync': 'Sync learning preferences and profiles to codeep.dev',
85
+ // 2.0 — surfaced for `/` autocomplete; documented in /help too.
86
+ 'compact': 'Summarize older messages to free up context',
87
+ 'commands': 'List custom slash commands in .codeep/commands/*.md',
88
+ 'checkpoint': 'Snapshot the session (conversation + provider/model + git HEAD)',
89
+ 'checkpoints': 'List saved checkpoints for this workspace',
90
+ 'rewind': 'Restore conversation from a saved checkpoint',
91
+ 'hooks': 'List installed lifecycle hooks (.codeep/hooks/<event>.sh)',
92
+ 'mcp': 'Manage MCP servers (browse, install, add, remove, resources, prompts)',
93
+ 'openrouter': 'Tune OpenRouter routing (preferred / ignore providers, fallbacks, privacy)',
85
94
  };
86
95
  import { helpCategories, keyboardShortcuts } from './components/Help.js';
87
96
  import { handleSettingsKey, SETTINGS } from './components/Settings.js';
@@ -142,7 +151,11 @@ export class App {
142
151
  // Inline menu state (renders below input/status)
143
152
  menuOpen = false;
144
153
  menuTitle = '';
154
+ /** Filtered view shown to the user; derived from `menuItemsAll` + `menuFilter`. */
145
155
  menuItems = [];
156
+ /** Full unfiltered list captured on `showSelect`. */
157
+ menuItemsAll = [];
158
+ menuFilter = '';
146
159
  menuIndex = 0;
147
160
  menuCurrentValue = '';
148
161
  menuCallback = null;
@@ -213,6 +226,10 @@ export class App {
213
226
  'provider', 'model', 'protocol', 'lang', 'grant', 'login', 'logout',
214
227
  'context-save', 'context-load', 'context-clear', 'learn',
215
228
  'cost', 'tasks', 'account', 'sync',
229
+ // 2.0 — extensions, checkpoints, MCP, custom commands, OpenRouter prefs.
230
+ // Keep in lockstep with COMMAND_DESCRIPTIONS below and helpCategories.
231
+ 'compact', 'commands', 'checkpoint', 'checkpoints', 'rewind',
232
+ 'hooks', 'mcp', 'openrouter',
216
233
  'c', 't', 'd', 'r', 'f', 'e', 'o', 'b', 'p',
217
234
  ];
218
235
  constructor(options) {
@@ -723,7 +740,9 @@ export class App {
723
740
  */
724
741
  showSelect(title, items, currentValue, callback) {
725
742
  this.menuTitle = title;
743
+ this.menuItemsAll = items;
726
744
  this.menuItems = items;
745
+ this.menuFilter = '';
727
746
  this.menuCurrentValue = currentValue;
728
747
  this.menuCallback = callback;
729
748
  this.menuOpen = true;
@@ -732,6 +751,27 @@ export class App {
732
751
  this.menuIndex = currentIndex >= 0 ? currentIndex : 0;
733
752
  this.scheduleRender();
734
753
  }
754
+ /**
755
+ * Recompute the visible menu list from the current filter string.
756
+ * Matches across key/label/description, case-insensitive. Keeps the
757
+ * current value highlighted if it survives the filter; otherwise
758
+ * cursor returns to the top.
759
+ */
760
+ applyMenuFilter(next) {
761
+ this.menuFilter = next;
762
+ if (!next) {
763
+ this.menuItems = this.menuItemsAll;
764
+ }
765
+ else {
766
+ const f = next.toLowerCase();
767
+ this.menuItems = this.menuItemsAll.filter((item) => item.key.toLowerCase().includes(f) ||
768
+ item.label.toLowerCase().includes(f) ||
769
+ (item.description?.toLowerCase().includes(f) ?? false));
770
+ }
771
+ const surviving = this.menuItems.findIndex((item) => item.key === this.menuCurrentValue);
772
+ this.menuIndex = surviving >= 0 ? surviving : 0;
773
+ this.scheduleRender();
774
+ }
735
775
  /**
736
776
  * Handle keyboard input
737
777
  */
@@ -1157,10 +1197,14 @@ export class App {
1157
1197
  const callback = this.menuCallback;
1158
1198
  this.menuOpen = false;
1159
1199
  this.menuCallback = null;
1200
+ this.menuFilter = '';
1201
+ this.menuItemsAll = [];
1160
1202
  if (selected && callback)
1161
1203
  callback(selected);
1162
1204
  },
1163
1205
  render: () => this.scheduleRender(),
1206
+ filter: this.menuFilter,
1207
+ setFilter: (v) => this.applyMenuFilter(v),
1164
1208
  });
1165
1209
  }
1166
1210
  /**
@@ -1524,7 +1568,10 @@ export class App {
1524
1568
  // Menu open (provider, model, lang, etc.)
1525
1569
  if (this.menuOpen) {
1526
1570
  this.screen.write(0, y, '> ', fg.gray);
1527
- this.screen.write(2, y, 'Select an option below...', fg.yellow);
1571
+ const hint = this.menuItemsAll.length > 10
1572
+ ? 'Type to filter, ↑↓ to navigate, Enter to pick…'
1573
+ : 'Select an option below…';
1574
+ this.screen.write(2, y, hint, fg.yellow);
1528
1575
  this.screen.showCursor(false);
1529
1576
  return;
1530
1577
  }
@@ -1669,7 +1716,7 @@ export class App {
1669
1716
  */
1670
1717
  renderInlineMenu(startY, width) {
1671
1718
  const items = this.menuItems;
1672
- const maxVisible = Math.min(items.length, 10);
1719
+ const maxVisible = Math.min(Math.max(items.length, 1), 10);
1673
1720
  // Calculate visible range with scroll
1674
1721
  let visibleStart = 0;
1675
1722
  if (items.length > maxVisible) {
@@ -1681,6 +1728,17 @@ export class App {
1681
1728
  this.screen.horizontalLine(y++, '─', PRIMARY_COLOR);
1682
1729
  // Title
1683
1730
  this.screen.writeLine(y++, this.menuTitle, PRIMARY_COLOR + style.bold);
1731
+ // Filter line — only rendered while filtering, so the menu looks
1732
+ // identical to the pre-filter UI by default.
1733
+ if (this.menuFilter) {
1734
+ const matchInfo = items.length === 0
1735
+ ? ' (no matches)'
1736
+ : ` (${items.length}/${this.menuItemsAll.length})`;
1737
+ this.screen.writeLine(y++, `Filter: ${this.menuFilter}█${matchInfo}`, fg.cyan);
1738
+ }
1739
+ if (items.length === 0) {
1740
+ this.screen.writeLine(y++, ' No items match. Backspace to edit, Esc to clear.', fg.gray);
1741
+ }
1684
1742
  // Items
1685
1743
  for (let i = 0; i < visibleItems.length; i++) {
1686
1744
  const item = visibleItems[i];
@@ -1700,7 +1758,8 @@ export class App {
1700
1758
  }
1701
1759
  // Footer with navigation hints
1702
1760
  const scrollInfo = items.length > maxVisible ? ` (${visibleStart + 1}-${visibleStart + visibleItems.length}/${items.length})` : '';
1703
- this.screen.writeLine(y, `↑↓ navigate Enter select Esc cancel${scrollInfo}`, fg.gray);
1761
+ const escHint = this.menuFilter ? 'Esc clear · Esc Esc cancel' : 'Esc cancel';
1762
+ this.screen.writeLine(y, `↑↓ navigate • Enter select • type to filter • ${escHint}${scrollInfo}`, fg.gray);
1704
1763
  }
1705
1764
  /**
1706
1765
  * Render inline settings below status bar
@@ -2440,6 +2499,20 @@ export class App {
2440
2499
  continue;
2441
2500
  }
2442
2501
  }
2502
+ // Strikethrough: ~~text~~ — using the SGR strikethrough escape (\x1b[9m).
2503
+ // Widely supported in modern terminals (iTerm2, Kitty, WezTerm, Alacritty,
2504
+ // gnome-terminal, Windows Terminal). Falls back gracefully to the dim
2505
+ // text colour on terminals that don't render the SGR.
2506
+ if (text.slice(i, i + 2) === '~~') {
2507
+ const end = text.indexOf('~~', i + 2);
2508
+ if (end !== -1) {
2509
+ const inner = text.slice(i + 2, end);
2510
+ result += '\x1b[9m' + fg.rgb(140, 140, 140) + inner + '\x1b[0m';
2511
+ hasFormatting = true;
2512
+ i = end + 2;
2513
+ continue;
2514
+ }
2515
+ }
2443
2516
  result += text[i];
2444
2517
  i++;
2445
2518
  }
@@ -2479,6 +2552,25 @@ export class App {
2479
2552
  });
2480
2553
  continue;
2481
2554
  }
2555
+ // Blockquote: `> text` — render with a left accent bar (PRIMARY_COLOR)
2556
+ // and the body in a dimmer grey so it visually sits behind regular text.
2557
+ // Strips one level of `> ` so nested quotes render with their own bar.
2558
+ const quoteMatch = line.match(/^(\s*)>\s?(.*)$/);
2559
+ if (quoteMatch) {
2560
+ const indent = quoteMatch[1];
2561
+ const quoteText = quoteMatch[2];
2562
+ const { formatted, hasFormatting } = this.applyInlineMarkdown(quoteText);
2563
+ const body = hasFormatting ? formatted : quoteText;
2564
+ // Vertical bar + space, then dim grey body. Skip if the body is
2565
+ // empty so an isolated `>` renders cleanly.
2566
+ const barred = PRIMARY_COLOR + '│' + '\x1b[0m' + (body ? ' ' + fg.rgb(160, 160, 160) + body + '\x1b[0m' : '');
2567
+ lines.push({
2568
+ text: prefix + indent + barred,
2569
+ style: prefixStyle,
2570
+ raw: true,
2571
+ });
2572
+ continue;
2573
+ }
2482
2574
  // List items: - item or * item or numbered 1. item
2483
2575
  const listMatch = line.match(/^(\s*)([-*]|\d+\.)\s+(.+)$/);
2484
2576
  if (listMatch) {
@@ -2621,12 +2713,25 @@ export class App {
2621
2713
  // Provider selection
2622
2714
  this.screen.writeLine(y++, 'Select Provider', fg.cyan + style.bold);
2623
2715
  y++;
2716
+ // Name column width — pad so descriptions align in a column.
2717
+ // Description is clipped to remaining terminal width with an ellipsis;
2718
+ // on narrow panes the user still sees the name and a hint of the
2719
+ // description rather than a silent overrun past the right edge.
2720
+ const longestName = this.loginProviders.reduce((m, p) => Math.max(m, p.name.length), 0);
2721
+ const descStartX = 2 + longestName + 2;
2722
+ const descBudget = Math.max(0, width - descStartX - 1);
2624
2723
  for (let i = 0; i < this.loginProviders.length; i++) {
2625
2724
  const provider = this.loginProviders[i];
2626
2725
  const isSelected = i === this.loginProviderIndex;
2627
2726
  const prefix = isSelected ? '→ ' : ' ';
2628
2727
  this.screen.write(0, y, prefix, isSelected ? fg.green : '');
2629
- this.screen.write(2, y, provider.name, isSelected ? fg.green + style.bold : fg.white);
2728
+ this.screen.write(2, y, provider.name.padEnd(longestName + 2), isSelected ? fg.green + style.bold : fg.white);
2729
+ if (provider.description && descBudget > 0) {
2730
+ const desc = provider.description.length > descBudget
2731
+ ? provider.description.slice(0, Math.max(1, descBudget - 1)) + '…'
2732
+ : provider.description;
2733
+ this.screen.write(descStartX, y, desc, isSelected ? fg.white : fg.gray);
2734
+ }
2630
2735
  y++;
2631
2736
  }
2632
2737
  y++;
@@ -179,6 +179,11 @@ export async function executeAgentTask(task, dryRun, ctx) {
179
179
  dryRun,
180
180
  onRequestPermission,
181
181
  chatHistory: app.getChatHistory(),
182
+ // Route MCP-prefixed tool calls through the shared TUI session id.
183
+ // Servers were registered against this id at app startup (see
184
+ // renderer/main.ts) so the agent picks up any `.codeep/mcp_servers.json`
185
+ // entries plus global ones at runtime.
186
+ mcpSessionId: 'codeep-tui',
182
187
  onIteration: (iteration, message) => {
183
188
  app.updateAgentProgress(iteration);
184
189
  app.setAgentWaitingForAI(true); // Waiting for AI response between tool calls