codeep 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,7 +50,7 @@ custom slash commands, lifecycle hooks, checkpoints, `/cost`,
50
50
  ### Multi-Provider Support
51
51
  - **Z.AI (ZhipuAI)** — GLM models (Coding Plan & pay-per-use API, international & China)
52
52
  - **OpenAI** — GPT models (flagship, Mini, Nano)
53
- - **Anthropic** — Claude models (Opus, Sonnet, Haiku)
53
+ - **Anthropic** — Claude models (Fable, Opus, Sonnet, Haiku)
54
54
  - **DeepSeek** — DeepSeek models (Pro, Flash)
55
55
  - **Google AI** — Gemini models (Pro, Flash)
56
56
  - **MiniMax** — MiniMax models (Coding Plan & pay-per-use API, international & China)
@@ -2,7 +2,7 @@
2
2
  // Slash command handler for ACP sessions.
3
3
  // Mirrors CLI commands from renderer/commands.ts but returns plain text
4
4
  // responses (no TUI) suitable for streaming back via session/update.
5
- import { config, getCurrentProvider, getModelsForCurrentProvider, setProvider, setApiKey, isConfigured, listSessionsWithInfo, startNewSession, loadSession, saveSession, initializeAsProject, isManuallyInitializedProject, setProjectPermission, hasWritePermission, hasReadPermission, isTelemetryEnabled, telemetryForcedOffByEnv, } from '../config/index.js';
5
+ import { config, getCurrentProvider, getModelsForCurrentProvider, setProvider, setApiKey, isConfigured, listSessionsWithInfo, startNewSession, loadSession, saveSession, initializeAsProject, isManuallyInitializedProject, setProjectPermission, hasWritePermission, hasReadPermission, isTelemetryEnabled, telemetryForcedOffByEnv, isKeySyncEnabled, keySyncForcedOffByEnv, } from '../config/index.js';
6
6
  import { getProviderList, getProvider } from '../config/providers.js';
7
7
  import { getProjectContext } from '../utils/project.js';
8
8
  import { loadCustomCommands } from '../utils/customCommands.js';
@@ -237,6 +237,34 @@ export async function handleCommand(input, session, onChunk, abortSignal) {
237
237
  lines.push('', 'Toggle with `/telemetry on` | `/telemetry off`. Controls automatic uploads of usage stats, session transcripts, progress, and memory notes.');
238
238
  return { handled: true, response: lines.join('\n') };
239
239
  }
240
+ case 'keysync': {
241
+ const sub = args[0]?.toLowerCase();
242
+ const envOff = keySyncForcedOffByEnv();
243
+ if (sub === 'on' || sub === 'off') {
244
+ if (envOff) {
245
+ return { handled: true, response: 'Cloud key sync is forced **off** by the `CODEEP_NO_KEY_SYNC` env var — unset it to change this. The config flag can\'t override an env var.' };
246
+ }
247
+ config.set('syncKeysToCloud', sub === 'on');
248
+ return {
249
+ handled: true,
250
+ response: sub === 'on'
251
+ ? 'Cloud key sync **on** — `codeep account push`/`sync` will now upload/download API keys. Note: synced keys are stored server-readable on codeep.dev.'
252
+ : 'Cloud key sync **off** — API keys stay in your OS keychain only. (`codeep account purge-keys` wipes any keys already on the server.)',
253
+ };
254
+ }
255
+ if (sub && sub !== 'status') {
256
+ return { handled: true, response: 'Usage: `/keysync` · `/keysync on` · `/keysync off`' };
257
+ }
258
+ const flag = config.get('syncKeysToCloud') === true;
259
+ const lines = [
260
+ `**Cloud key sync:** ${isKeySyncEnabled() ? 'on' : 'off'}`,
261
+ `- Config flag \`syncKeysToCloud\`: ${flag}`,
262
+ ];
263
+ if (envOff)
264
+ lines.push('- Forced **off** by `CODEEP_NO_KEY_SYNC` (env overrides the flag).');
265
+ lines.push('', 'OFF by default — API keys live only in your OS keychain unless enabled. When on, `codeep account push`/`sync` move keys, stored **server-readable** on codeep.dev.');
266
+ return { handled: true, response: lines.join('\n') };
267
+ }
240
268
  case 'login': {
241
269
  const [providerId, apiKey] = args;
242
270
  if (!providerId || !apiKey) {
@@ -67,6 +67,9 @@ const AVAILABLE_COMMANDS = [
67
67
  { name: 'learn', description: 'Learn coding preferences from project files' },
68
68
  { name: 'memory', description: 'Project memory notes — add / list / remove / clear', input: { hint: '<note> | list | remove <n> | clear' } },
69
69
  { name: 'profile', description: 'Save / load / delete provider+model presets', input: { hint: 'save | load | delete | list | <name>' } },
70
+ // Privacy toggles
71
+ { name: 'telemetry', description: 'Show or toggle automatic cloud telemetry', input: { hint: '[on|off]' } },
72
+ { name: 'keysync', description: 'Show or toggle syncing API keys to codeep.dev (off by default)', input: { hint: '[on|off]' } },
70
73
  // Skills + custom commands
71
74
  { name: 'skills', description: 'List/create/share skill bundles. Subcommands: bundles, create-bundle, show, publish, install, browse, unpublish', input: { hint: '[query] | bundles | create-bundle <name> | show <name> | publish <slug> [--public] | install <owner>/<slug> | browse [q] | unpublish <owner>/<slug>' } },
72
75
  { name: 'commands', description: 'List user-authored commands from .codeep/commands/*.md' },
package/dist/api/index.js CHANGED
@@ -2,7 +2,7 @@ import * as http from 'node:http';
2
2
  import * as https from 'node:https';
3
3
  import { config, getApiKey, resolveBaseUrl } from '../config/index.js';
4
4
  import { withRetry, isNetworkError } from '../utils/retry.js';
5
- import { getProvider, getProviderBaseUrl, getProviderAuthHeader, usesMaxCompletionTokens, requiresDefaultTemperature } from '../config/providers.js';
5
+ import { getProvider, getProviderBaseUrl, getProviderAuthHeader, usesMaxCompletionTokens, requiresDefaultTemperature, modelRejectsSamplingParams } from '../config/providers.js';
6
6
  import { logApiRequest, logApiResponse } from '../utils/logger.js';
7
7
  import { loadProjectIntelligence, generateContextFromIntelligence } from '../utils/projectIntelligence.js';
8
8
  import { loadProjectRules } from '../utils/agent.js';
@@ -655,7 +655,9 @@ async function chatAnthropic(message, history, model, apiKey, onChunk, abortSign
655
655
  model,
656
656
  messages,
657
657
  max_tokens: maxTokens,
658
- temperature,
658
+ // Fable 5 / Opus 4.7+ reject temperature with a 400 — omit it there
659
+ // (omission means API default on every Claude model).
660
+ ...(modelRejectsSamplingParams(model) ? {} : { temperature }),
659
661
  stream,
660
662
  ...cachedSystem,
661
663
  }),
@@ -68,6 +68,7 @@ export declare function usesMaxCompletionTokens(providerId: string): boolean;
68
68
  * (e.g. OpenAI GPT-5+ only accepts the default of 1).
69
69
  */
70
70
  export declare function requiresDefaultTemperature(providerId: string): boolean;
71
+ export declare function modelRejectsSamplingParams(model: string): boolean;
71
72
  /**
72
73
  * Returns the effective max output tokens for a provider, capped by the provider's limit.
73
74
  * Falls back to the requested value if no provider limit is set.
@@ -243,9 +243,8 @@ export const PROVIDERS = {
243
243
  },
244
244
  },
245
245
  models: [
246
- { id: 'claude-opus-4-8', name: 'Claude Opus 4.8', description: 'Most capable Claude model' },
247
- { id: 'claude-opus-4-7', name: 'Claude Opus 4.7', description: 'Previous generation Opus' },
248
- { id: 'claude-opus-4-6', name: 'Claude Opus 4.6', description: 'Older generation Opus' },
246
+ { id: 'claude-fable-5', name: 'Claude Fable 5', description: 'Most powerful — new tier above Opus' },
247
+ { id: 'claude-opus-4-8', name: 'Claude Opus 4.8', description: 'Most capable Opus model' },
249
248
  { id: 'claude-sonnet-4-6', name: 'Claude Sonnet', description: 'Best balance of speed and intelligence' },
250
249
  { id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku', description: 'Fastest and most affordable' },
251
250
  ],
@@ -448,6 +447,17 @@ export function usesMaxCompletionTokens(providerId) {
448
447
  export function requiresDefaultTemperature(providerId) {
449
448
  return PROVIDERS[providerId]?.requiresDefaultTemperature ?? false;
450
449
  }
450
+ /**
451
+ * Models that reject sampling parameters (temperature/top_p/top_k) with a 400.
452
+ * Anthropic removed them on Fable 5 and Opus 4.7+; older Claude models still
453
+ * accept them, so this must be a MODEL-level check, not a provider-level one
454
+ * (requiresDefaultTemperature can't express it). Omitting the field is always
455
+ * safe — the API treats omission as default.
456
+ */
457
+ const SAMPLING_PARAMS_REJECTED = ['claude-fable-5', 'claude-opus-4-8', 'claude-opus-4-7'];
458
+ export function modelRejectsSamplingParams(model) {
459
+ return SAMPLING_PARAMS_REJECTED.some(id => model === id || model.startsWith(`${id}-`));
460
+ }
451
461
  /**
452
462
  * Returns the effective max output tokens for a provider, capped by the provider's limit.
453
463
  * Falls back to the requested value if no provider limit is set.
@@ -80,7 +80,7 @@ const COMMAND_DESCRIPTIONS = {
80
80
  'learn': 'Learn code preferences',
81
81
  'cost': 'Show session cost and token usage',
82
82
  'profile': 'Save/load settings profiles',
83
- 'tasks': 'Show pending tasks from codeep.dev dashboard',
83
+ 'tasks': 'List/add/done/delete codeep.dev tasks — add <title> [--bug|--feature]',
84
84
  'sync': 'Sync learning preferences and profiles to codeep.dev',
85
85
  'telemetry': 'Show or toggle automatic cloud telemetry (on/off)',
86
86
  'keysync': 'Show or toggle syncing API keys to codeep.dev (on/off)',
@@ -234,7 +234,7 @@ export class App {
234
234
  'multiline', 'memory', 'init',
235
235
  'provider', 'model', 'protocol', 'lang', 'grant', 'login', 'logout',
236
236
  'context-save', 'context-load', 'context-clear', 'learn',
237
- 'cost', 'tasks', 'account', 'sync', 'telemetry',
237
+ 'cost', 'tasks', 'account', 'sync', 'keysync', 'telemetry',
238
238
  // 2.0 — extensions, checkpoints, MCP, custom commands, OpenRouter prefs.
239
239
  // Keep in lockstep with COMMAND_DESCRIPTIONS below and helpCategories.
240
240
  'compact', 'commands', 'checkpoint', 'checkpoints', 'rewind',
@@ -1833,11 +1833,40 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
1833
1833
  }
1834
1834
  break;
1835
1835
  }
1836
- // /tasks add <title> — create a new task on the dashboard
1836
+ // /tasks add <title> [--bug | --feature] [--desc <text>] — create a task
1837
+ // on the dashboard. Type matches the dashboard picker (task | bug |
1838
+ // feature); a --bug/--feature/--task flag anywhere sets it (default task).
1839
+ // --desc/--description captures the following words (until the next flag)
1840
+ // as the description — the same field the dashboard + macOS app set, and
1841
+ // which the list view and the agent task-context prompt already render.
1837
1842
  if (subCmd === 'add') {
1838
- const title = args.slice(1).join(' ').trim();
1843
+ const TASK_TYPES = ['task', 'bug', 'feature'];
1844
+ let type = 'task';
1845
+ const titleWords = [];
1846
+ const descWords = [];
1847
+ let capturingDesc = false;
1848
+ for (const w of args.slice(1)) {
1849
+ const flag = /^--([\w-]+)$/.exec(w);
1850
+ if (flag) {
1851
+ const name = flag[1].toLowerCase();
1852
+ if (name === 'desc' || name === 'description') {
1853
+ capturingDesc = true;
1854
+ continue;
1855
+ }
1856
+ if (TASK_TYPES.includes(name))
1857
+ type = name;
1858
+ capturingDesc = false; // any non-desc flag ends description capture
1859
+ continue;
1860
+ }
1861
+ if (capturingDesc)
1862
+ descWords.push(w);
1863
+ else
1864
+ titleWords.push(w);
1865
+ }
1866
+ const title = titleWords.join(' ').trim();
1867
+ const description = descWords.join(' ').trim();
1839
1868
  if (!title) {
1840
- ctx.app.notify('Usage: /tasks add <title>');
1869
+ ctx.app.notify('Usage: /tasks add <title> [--bug | --feature] [--desc <text>]');
1841
1870
  break;
1842
1871
  }
1843
1872
  const projectName = ctx.projectContext?.name;
@@ -1852,10 +1881,10 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
1852
1881
  const res = await fetch('https://codeep.dev/api/tasks', {
1853
1882
  method: 'POST',
1854
1883
  headers: { 'Content-Type': 'application/json', 'x-sync-token': syncToken },
1855
- body: JSON.stringify({ projectName: projectName || '', projectId: projectId ?? null, title, type: 'task' }),
1884
+ body: JSON.stringify({ projectName: projectName || '', projectId: projectId ?? null, title, type, ...(description ? { description } : {}) }),
1856
1885
  });
1857
1886
  if (res.ok) {
1858
- ctx.app.notify(`+ Task added: ${title}`);
1887
+ ctx.app.notify(`+ ${type[0].toUpperCase()}${type.slice(1)} added: ${title}`);
1859
1888
  }
1860
1889
  else {
1861
1890
  ctx.app.notify('Failed to add task');
@@ -1884,7 +1913,10 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
1884
1913
  const lines = [`## Tasks${projectName ? ` — ${projectName}` : ''}`, ''];
1885
1914
  tasks.forEach((t, i) => {
1886
1915
  const icon = TYPE_ICON[t.type] ?? '[task]';
1887
- lines.push(`${i + 1}. ${icon} ${t.title}${t.description ? `\n ${t.description}` : ''}`);
1916
+ // In a global listing (not scoped to one project) tag each row with its
1917
+ // project so a mixed list is legible — matches the macOS/web task rows.
1918
+ const proj = !projectName && t.project_name ? ` _(${t.project_name})_` : '';
1919
+ lines.push(`${i + 1}. ${icon} ${t.title}${proj}${t.description ? `\n ${t.description}` : ''}`);
1888
1920
  });
1889
1921
  lines.push('', `*${tasks.length} pending task${tasks.length > 1 ? 's' : ''}. Use /tasks done <n> to mark complete.*`);
1890
1922
  lines.push('*Tasks loaded into agent context — agent will see them in the next message.*');
@@ -2034,9 +2066,12 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
2034
2066
  });
2035
2067
  break;
2036
2068
  }
2037
- case 'stats':
2038
- case 'cost': {
2039
- const { getCostBreakdown, getSessionStats, formatTokenCount, getPricingTable } = await import('../utils/tokenTracker.js');
2069
+ // /stats — detailed session view: per-model breakdown, total, prompt-cache
2070
+ // summary, and the per-1M pricing reference. `/cost` is the concise sibling
2071
+ // (formatCostReport, above); the two are intentionally distinct, so this
2072
+ // case no longer also claims 'cost' (which always hit the handler above).
2073
+ case 'stats': {
2074
+ const { getCostBreakdown, getSessionStats, formatTokenCount, getPricingTable, getCacheStats } = await import('../utils/tokenTracker.js');
2040
2075
  const stats = getSessionStats();
2041
2076
  const lines = ['## Session Cost', ''];
2042
2077
  if (stats.requestCount === 0) {
@@ -2064,6 +2099,19 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
2064
2099
  lines.push(`**Total: ~$${stats.estimatedCost.toFixed(4)}**`);
2065
2100
  }
2066
2101
  }
2102
+ // Prompt caching — parity with /cost (the 2.0.2 caching section was
2103
+ // only wired into formatCostReport). Shown only when caching landed.
2104
+ const cache = getCacheStats();
2105
+ if (cache.cacheReadTokens > 0 || cache.cacheCreationTokens > 0) {
2106
+ lines.push('', '### Prompt caching');
2107
+ lines.push(`Cache reads: ${formatTokenCount(cache.cacheReadTokens)} tokens (billed at 0.1× input rate)`);
2108
+ if (cache.cacheCreationTokens > 0) {
2109
+ lines.push(`Cache writes: ${formatTokenCount(cache.cacheCreationTokens)} tokens (billed at 1.25× input rate)`);
2110
+ }
2111
+ if (cache.estimatedSavingsUsd > 0) {
2112
+ lines.push(`Estimated savings vs no caching: $${cache.estimatedSavingsUsd.toFixed(4)}`);
2113
+ }
2114
+ }
2067
2115
  lines.push('');
2068
2116
  }
2069
2117
  lines.push('### Pricing (per 1M tokens)');
@@ -17,7 +17,7 @@ import { createHash } from 'crypto';
17
17
  import { config, getApiKey, resolveBaseUrl } from '../config/index.js';
18
18
  import { loadProjectIntelligence, generateContextFromIntelligence } from './projectIntelligence.js';
19
19
  import { syncProgress, generateProjectId } from './codeepCloud.js';
20
- import { getProviderAuthHeader, supportsNativeTools, getEffectiveMaxTokens, usesMaxCompletionTokens, requiresDefaultTemperature, isNoApiKeyProvider } from '../config/providers.js';
20
+ import { getProviderAuthHeader, supportsNativeTools, getEffectiveMaxTokens, usesMaxCompletionTokens, requiresDefaultTemperature, modelRejectsSamplingParams, isNoApiKeyProvider } from '../config/providers.js';
21
21
  import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from './tokenTracker.js';
22
22
  import { parseOpenAIToolCalls, parseAnthropicToolCalls, parseToolCalls } from './toolParsing.js';
23
23
  import { formatToolDefinitions, getOpenAITools, getAnthropicTools } from './tools.js';
@@ -357,7 +357,9 @@ additionalTools) {
357
357
  let endpoint;
358
358
  let body;
359
359
  const useStreaming = Boolean(onChunk);
360
- const tempParam = requiresDefaultTemperature(providerId) ? {} : { temperature: config.get('temperature') };
360
+ // Provider-level guard (OpenAI GPT-5+) OR model-level guard — Anthropic's
361
+ // Fable 5 / Opus 4.7+ reject temperature with a 400; omission is safe.
362
+ const tempParam = (requiresDefaultTemperature(providerId) || modelRejectsSamplingParams(model)) ? {} : { temperature: config.get('temperature') };
361
363
  if (protocol === 'openai') {
362
364
  const maxTok = getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384));
363
365
  const tokParam = usesMaxCompletionTokens(providerId) ? { max_completion_tokens: maxTok } : { max_tokens: maxTok };
@@ -549,7 +551,9 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
549
551
  try {
550
552
  let endpoint;
551
553
  let body;
552
- const tempParam = requiresDefaultTemperature(providerId) ? {} : { temperature: config.get('temperature') };
554
+ // Provider-level guard (OpenAI GPT-5+) OR model-level guard — Anthropic's
555
+ // Fable 5 / Opus 4.7+ reject temperature with a 400; omission is safe.
556
+ const tempParam = (requiresDefaultTemperature(providerId) || modelRejectsSamplingParams(model)) ? {} : { temperature: config.get('temperature') };
553
557
  if (protocol === 'openai') {
554
558
  const maxTok = getEffectiveMaxTokens(providerId, Math.max(config.get('maxTokens'), 16384));
555
559
  const tokParam = usesMaxCompletionTokens(providerId) ? { max_completion_tokens: maxTok } : { max_tokens: maxTok };
@@ -16,9 +16,8 @@ const MODEL_CONTEXT_WINDOWS = {
16
16
  'gpt-5.4-mini': 400_000,
17
17
  'gpt-5.4-nano': 400_000,
18
18
  // Anthropic
19
+ 'claude-fable-5': 1_000_000,
19
20
  'claude-opus-4-8': 1_000_000,
20
- 'claude-opus-4-7': 1_000_000,
21
- 'claude-opus-4-6': 1_000_000,
22
21
  'claude-sonnet-4-6': 1_000_000,
23
22
  'claude-haiku-4-5-20251001': 200_000,
24
23
  // DeepSeek
@@ -52,9 +51,8 @@ const MODEL_PRICING = {
52
51
  'gpt-5.4-mini': { inputPer1M: 0.75, outputPer1M: 4.50 },
53
52
  'gpt-5.4-nano': { inputPer1M: 0.20, outputPer1M: 1.25 },
54
53
  // Anthropic
54
+ 'claude-fable-5': { inputPer1M: 10.00, outputPer1M: 50.00 },
55
55
  'claude-opus-4-8': { inputPer1M: 5.00, outputPer1M: 25.00 },
56
- 'claude-opus-4-7': { inputPer1M: 5.00, outputPer1M: 25.00 },
57
- 'claude-opus-4-6': { inputPer1M: 5.00, outputPer1M: 25.00 },
58
56
  'claude-sonnet-4-6': { inputPer1M: 3.00, outputPer1M: 15.00 },
59
57
  'claude-haiku-4-5-20251001': { inputPer1M: 1.00, outputPer1M: 5.00 },
60
58
  // DeepSeek (cache-miss input pricing)
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "2.8.0";
1
+ export declare const VERSION = "2.10.0";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/gen-version.js — do not edit by hand.
2
2
  // Baked from package.json at build time so the bun-compiled binary reports
3
3
  // the right version (it has no package.json on disk to read at runtime).
4
- export const VERSION = '2.8.0';
4
+ export const VERSION = '2.10.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
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,7 +9,7 @@
9
9
  },
10
10
  "scripts": {
11
11
  "dev": "node scripts/gen-version.js && node --import tsx src/renderer/main.ts",
12
- "prepack": "node scripts/gen-version.js && tsc; node scripts/fix-imports.js",
12
+ "prepack": "node scripts/gen-version.js && tsc && node scripts/fix-imports.js",
13
13
  "build": "node scripts/gen-version.js && tsc && node scripts/fix-imports.js",
14
14
  "start": "node dist/renderer/main.js",
15
15
  "demo:renderer": "node --import tsx src/renderer/demo.ts",