codeep 1.3.41 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) 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 +46 -1
  10. package/dist/config/providers.js +76 -1
  11. package/dist/renderer/App.d.ts +12 -0
  12. package/dist/renderer/App.js +96 -4
  13. package/dist/renderer/agentExecution.js +5 -0
  14. package/dist/renderer/commands.js +348 -2
  15. package/dist/renderer/components/Login.d.ts +1 -0
  16. package/dist/renderer/components/Login.js +24 -9
  17. package/dist/renderer/handlers.d.ts +11 -1
  18. package/dist/renderer/handlers.js +30 -0
  19. package/dist/renderer/main.js +73 -0
  20. package/dist/utils/agent.d.ts +17 -0
  21. package/dist/utils/agent.js +91 -7
  22. package/dist/utils/agentChat.d.ts +10 -2
  23. package/dist/utils/agentChat.js +48 -9
  24. package/dist/utils/agentStream.js +6 -2
  25. package/dist/utils/checkpoints.d.ts +93 -0
  26. package/dist/utils/checkpoints.js +205 -0
  27. package/dist/utils/context.d.ts +24 -0
  28. package/dist/utils/context.js +57 -0
  29. package/dist/utils/customCommands.d.ts +62 -0
  30. package/dist/utils/customCommands.js +201 -0
  31. package/dist/utils/hooks.d.ts +97 -0
  32. package/dist/utils/hooks.js +223 -0
  33. package/dist/utils/mcpClient.d.ts +229 -0
  34. package/dist/utils/mcpClient.js +497 -0
  35. package/dist/utils/mcpConfig.d.ts +55 -0
  36. package/dist/utils/mcpConfig.js +177 -0
  37. package/dist/utils/mcpMarketplace.d.ts +49 -0
  38. package/dist/utils/mcpMarketplace.js +175 -0
  39. package/dist/utils/mcpRegistry.d.ts +129 -0
  40. package/dist/utils/mcpRegistry.js +427 -0
  41. package/dist/utils/mcpSamplingBridge.d.ts +32 -0
  42. package/dist/utils/mcpSamplingBridge.js +88 -0
  43. package/dist/utils/mcpStreamableHttp.d.ts +65 -0
  44. package/dist/utils/mcpStreamableHttp.js +207 -0
  45. package/dist/utils/openrouterPrefs.d.ts +36 -0
  46. package/dist/utils/openrouterPrefs.js +83 -0
  47. package/dist/utils/skillBundles.d.ts +84 -0
  48. package/dist/utils/skillBundles.js +257 -0
  49. package/dist/utils/skillBundlesCloud.d.ts +66 -0
  50. package/dist/utils/skillBundlesCloud.js +196 -0
  51. package/dist/utils/tokenTracker.d.ts +14 -2
  52. package/dist/utils/tokenTracker.js +59 -45
  53. package/dist/utils/toolExecution.d.ts +17 -1
  54. package/dist/utils/toolExecution.js +184 -6
  55. package/dist/utils/tools.d.ts +22 -6
  56. package/dist/utils/tools.js +83 -8
  57. package/package.json +3 -2
  58. package/bin/codeep-macos-arm64 +0 -0
  59. 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
  */
@@ -142,7 +142,11 @@ export class App {
142
142
  // Inline menu state (renders below input/status)
143
143
  menuOpen = false;
144
144
  menuTitle = '';
145
+ /** Filtered view shown to the user; derived from `menuItemsAll` + `menuFilter`. */
145
146
  menuItems = [];
147
+ /** Full unfiltered list captured on `showSelect`. */
148
+ menuItemsAll = [];
149
+ menuFilter = '';
146
150
  menuIndex = 0;
147
151
  menuCurrentValue = '';
148
152
  menuCallback = null;
@@ -723,7 +727,9 @@ export class App {
723
727
  */
724
728
  showSelect(title, items, currentValue, callback) {
725
729
  this.menuTitle = title;
730
+ this.menuItemsAll = items;
726
731
  this.menuItems = items;
732
+ this.menuFilter = '';
727
733
  this.menuCurrentValue = currentValue;
728
734
  this.menuCallback = callback;
729
735
  this.menuOpen = true;
@@ -732,6 +738,27 @@ export class App {
732
738
  this.menuIndex = currentIndex >= 0 ? currentIndex : 0;
733
739
  this.scheduleRender();
734
740
  }
741
+ /**
742
+ * Recompute the visible menu list from the current filter string.
743
+ * Matches across key/label/description, case-insensitive. Keeps the
744
+ * current value highlighted if it survives the filter; otherwise
745
+ * cursor returns to the top.
746
+ */
747
+ applyMenuFilter(next) {
748
+ this.menuFilter = next;
749
+ if (!next) {
750
+ this.menuItems = this.menuItemsAll;
751
+ }
752
+ else {
753
+ const f = next.toLowerCase();
754
+ this.menuItems = this.menuItemsAll.filter((item) => item.key.toLowerCase().includes(f) ||
755
+ item.label.toLowerCase().includes(f) ||
756
+ (item.description?.toLowerCase().includes(f) ?? false));
757
+ }
758
+ const surviving = this.menuItems.findIndex((item) => item.key === this.menuCurrentValue);
759
+ this.menuIndex = surviving >= 0 ? surviving : 0;
760
+ this.scheduleRender();
761
+ }
735
762
  /**
736
763
  * Handle keyboard input
737
764
  */
@@ -1157,10 +1184,14 @@ export class App {
1157
1184
  const callback = this.menuCallback;
1158
1185
  this.menuOpen = false;
1159
1186
  this.menuCallback = null;
1187
+ this.menuFilter = '';
1188
+ this.menuItemsAll = [];
1160
1189
  if (selected && callback)
1161
1190
  callback(selected);
1162
1191
  },
1163
1192
  render: () => this.scheduleRender(),
1193
+ filter: this.menuFilter,
1194
+ setFilter: (v) => this.applyMenuFilter(v),
1164
1195
  });
1165
1196
  }
1166
1197
  /**
@@ -1524,7 +1555,10 @@ export class App {
1524
1555
  // Menu open (provider, model, lang, etc.)
1525
1556
  if (this.menuOpen) {
1526
1557
  this.screen.write(0, y, '> ', fg.gray);
1527
- this.screen.write(2, y, 'Select an option below...', fg.yellow);
1558
+ const hint = this.menuItemsAll.length > 10
1559
+ ? 'Type to filter, ↑↓ to navigate, Enter to pick…'
1560
+ : 'Select an option below…';
1561
+ this.screen.write(2, y, hint, fg.yellow);
1528
1562
  this.screen.showCursor(false);
1529
1563
  return;
1530
1564
  }
@@ -1669,7 +1703,7 @@ export class App {
1669
1703
  */
1670
1704
  renderInlineMenu(startY, width) {
1671
1705
  const items = this.menuItems;
1672
- const maxVisible = Math.min(items.length, 10);
1706
+ const maxVisible = Math.min(Math.max(items.length, 1), 10);
1673
1707
  // Calculate visible range with scroll
1674
1708
  let visibleStart = 0;
1675
1709
  if (items.length > maxVisible) {
@@ -1681,6 +1715,17 @@ export class App {
1681
1715
  this.screen.horizontalLine(y++, '─', PRIMARY_COLOR);
1682
1716
  // Title
1683
1717
  this.screen.writeLine(y++, this.menuTitle, PRIMARY_COLOR + style.bold);
1718
+ // Filter line — only rendered while filtering, so the menu looks
1719
+ // identical to the pre-filter UI by default.
1720
+ if (this.menuFilter) {
1721
+ const matchInfo = items.length === 0
1722
+ ? ' (no matches)'
1723
+ : ` (${items.length}/${this.menuItemsAll.length})`;
1724
+ this.screen.writeLine(y++, `Filter: ${this.menuFilter}█${matchInfo}`, fg.cyan);
1725
+ }
1726
+ if (items.length === 0) {
1727
+ this.screen.writeLine(y++, ' No items match. Backspace to edit, Esc to clear.', fg.gray);
1728
+ }
1684
1729
  // Items
1685
1730
  for (let i = 0; i < visibleItems.length; i++) {
1686
1731
  const item = visibleItems[i];
@@ -1700,7 +1745,8 @@ export class App {
1700
1745
  }
1701
1746
  // Footer with navigation hints
1702
1747
  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);
1748
+ const escHint = this.menuFilter ? 'Esc clear · Esc Esc cancel' : 'Esc cancel';
1749
+ this.screen.writeLine(y, `↑↓ navigate • Enter select • type to filter • ${escHint}${scrollInfo}`, fg.gray);
1704
1750
  }
1705
1751
  /**
1706
1752
  * Render inline settings below status bar
@@ -2440,6 +2486,20 @@ export class App {
2440
2486
  continue;
2441
2487
  }
2442
2488
  }
2489
+ // Strikethrough: ~~text~~ — using the SGR strikethrough escape (\x1b[9m).
2490
+ // Widely supported in modern terminals (iTerm2, Kitty, WezTerm, Alacritty,
2491
+ // gnome-terminal, Windows Terminal). Falls back gracefully to the dim
2492
+ // text colour on terminals that don't render the SGR.
2493
+ if (text.slice(i, i + 2) === '~~') {
2494
+ const end = text.indexOf('~~', i + 2);
2495
+ if (end !== -1) {
2496
+ const inner = text.slice(i + 2, end);
2497
+ result += '\x1b[9m' + fg.rgb(140, 140, 140) + inner + '\x1b[0m';
2498
+ hasFormatting = true;
2499
+ i = end + 2;
2500
+ continue;
2501
+ }
2502
+ }
2443
2503
  result += text[i];
2444
2504
  i++;
2445
2505
  }
@@ -2479,6 +2539,25 @@ export class App {
2479
2539
  });
2480
2540
  continue;
2481
2541
  }
2542
+ // Blockquote: `> text` — render with a left accent bar (PRIMARY_COLOR)
2543
+ // and the body in a dimmer grey so it visually sits behind regular text.
2544
+ // Strips one level of `> ` so nested quotes render with their own bar.
2545
+ const quoteMatch = line.match(/^(\s*)>\s?(.*)$/);
2546
+ if (quoteMatch) {
2547
+ const indent = quoteMatch[1];
2548
+ const quoteText = quoteMatch[2];
2549
+ const { formatted, hasFormatting } = this.applyInlineMarkdown(quoteText);
2550
+ const body = hasFormatting ? formatted : quoteText;
2551
+ // Vertical bar + space, then dim grey body. Skip if the body is
2552
+ // empty so an isolated `>` renders cleanly.
2553
+ const barred = PRIMARY_COLOR + '│' + '\x1b[0m' + (body ? ' ' + fg.rgb(160, 160, 160) + body + '\x1b[0m' : '');
2554
+ lines.push({
2555
+ text: prefix + indent + barred,
2556
+ style: prefixStyle,
2557
+ raw: true,
2558
+ });
2559
+ continue;
2560
+ }
2482
2561
  // List items: - item or * item or numbered 1. item
2483
2562
  const listMatch = line.match(/^(\s*)([-*]|\d+\.)\s+(.+)$/);
2484
2563
  if (listMatch) {
@@ -2621,12 +2700,25 @@ export class App {
2621
2700
  // Provider selection
2622
2701
  this.screen.writeLine(y++, 'Select Provider', fg.cyan + style.bold);
2623
2702
  y++;
2703
+ // Name column width — pad so descriptions align in a column.
2704
+ // Description is clipped to remaining terminal width with an ellipsis;
2705
+ // on narrow panes the user still sees the name and a hint of the
2706
+ // description rather than a silent overrun past the right edge.
2707
+ const longestName = this.loginProviders.reduce((m, p) => Math.max(m, p.name.length), 0);
2708
+ const descStartX = 2 + longestName + 2;
2709
+ const descBudget = Math.max(0, width - descStartX - 1);
2624
2710
  for (let i = 0; i < this.loginProviders.length; i++) {
2625
2711
  const provider = this.loginProviders[i];
2626
2712
  const isSelected = i === this.loginProviderIndex;
2627
2713
  const prefix = isSelected ? '→ ' : ' ';
2628
2714
  this.screen.write(0, y, prefix, isSelected ? fg.green : '');
2629
- this.screen.write(2, y, provider.name, isSelected ? fg.green + style.bold : fg.white);
2715
+ this.screen.write(2, y, provider.name.padEnd(longestName + 2), isSelected ? fg.green + style.bold : fg.white);
2716
+ if (provider.description && descBudget > 0) {
2717
+ const desc = provider.description.length > descBudget
2718
+ ? provider.description.slice(0, Math.max(1, descBudget - 1)) + '…'
2719
+ : provider.description;
2720
+ this.screen.write(descStartX, y, desc, isSelected ? fg.white : fg.gray);
2721
+ }
2630
2722
  y++;
2631
2723
  }
2632
2724
  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