codeep 2.7.0 → 2.9.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)
@@ -1443,6 +1443,7 @@ function buildHelp() {
1443
1443
  '| `/login <provider> <key>` | Set API key for a provider |',
1444
1444
  '| `/apikey [key]` | Show or set API key |',
1445
1445
  '| `/telemetry [on\\|off]` | Show or toggle automatic cloud telemetry |',
1446
+ '| `/keysync [on\\|off]` | Show or toggle syncing API keys to codeep.dev |',
1446
1447
  '| `/lang [code]` | Set response language (`en`, `hr`, `auto`…) |',
1447
1448
  '| `/grant` | Grant write access for workspace |',
1448
1449
  '',
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
  }),
@@ -104,6 +104,12 @@ interface ConfigSchema {
104
104
  * transcripts, progress, memory notes). Default true; set false to opt out.
105
105
  * The CODEEP_NO_TELEMETRY / DO_NOT_TRACK env vars also force it off. */
106
106
  telemetry: boolean;
107
+ /** Opt-in to syncing API keys to codeep.dev (`codeep account push`/`sync`).
108
+ * OFF by default — keys live only in the OS keychain unless you enable this.
109
+ * Synced keys are stored server-readable (AES from a server-held secret), so
110
+ * this is an explicit consent switch. Enable via `/keysync on` or Settings;
111
+ * the CODEEP_NO_KEY_SYNC env var forces it off (org-policy hard switch). */
112
+ syncKeysToCloud: boolean;
107
113
  githubId: string;
108
114
  githubUsername: string;
109
115
  syncToken: string;
@@ -176,6 +182,13 @@ export declare function isTelemetryEnabled(): boolean;
176
182
  * the /telemetry command explain why a toggle had no effect.
177
183
  */
178
184
  export declare function telemetryForcedOffByEnv(): boolean;
185
+ export declare function isKeySyncEnabled(): boolean;
186
+ /**
187
+ * True when CODEEP_NO_KEY_SYNC is forcing key sync off — so the `syncKeysToCloud`
188
+ * flag can't turn it back on. Lets the /keysync command explain why a toggle had
189
+ * no effect.
190
+ */
191
+ export declare function keySyncForcedOffByEnv(): boolean;
179
192
  /**
180
193
  * Clear API key for a specific provider
181
194
  */
@@ -188,6 +188,7 @@ function createConfig() {
188
188
  keysSecured: false,
189
189
  apiKeys: {},
190
190
  telemetry: true,
191
+ syncKeysToCloud: false,
191
192
  githubId: '',
192
193
  githubUsername: '',
193
194
  syncToken: '',
@@ -550,6 +551,30 @@ export function isTelemetryEnabled() {
550
551
  export function telemetryForcedOffByEnv() {
551
552
  return envForcesTelemetryOff();
552
553
  }
554
+ /**
555
+ * Whether syncing API keys to the cloud is allowed. OFF by default (opt-in):
556
+ * unlike telemetry, this also gates the EXPLICIT `codeep account push` and the
557
+ * key-download half of `account sync`, because pushing a key stores it
558
+ * server-readable. The CODEEP_NO_KEY_SYNC env var forces it off as an
559
+ * org-policy hard switch the config flag can't override.
560
+ */
561
+ function envForcesKeySyncOff() {
562
+ const off = (v) => !!v && !/^(0|false|no|off)$/i.test(v.trim());
563
+ return off(process.env.CODEEP_NO_KEY_SYNC);
564
+ }
565
+ export function isKeySyncEnabled() {
566
+ if (envForcesKeySyncOff())
567
+ return false;
568
+ return config.get('syncKeysToCloud') === true; // default OFF — must be explicitly true
569
+ }
570
+ /**
571
+ * True when CODEEP_NO_KEY_SYNC is forcing key sync off — so the `syncKeysToCloud`
572
+ * flag can't turn it back on. Lets the /keysync command explain why a toggle had
573
+ * no effect.
574
+ */
575
+ export function keySyncForcedOffByEnv() {
576
+ return envForcesKeySyncOff();
577
+ }
553
578
  /**
554
579
  * Clear API key for a specific provider
555
580
  */
@@ -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.
@@ -83,6 +83,7 @@ const COMMAND_DESCRIPTIONS = {
83
83
  'tasks': 'Show pending tasks from codeep.dev dashboard',
84
84
  'sync': 'Sync learning preferences and profiles to codeep.dev',
85
85
  'telemetry': 'Show or toggle automatic cloud telemetry (on/off)',
86
+ 'keysync': 'Show or toggle syncing API keys to codeep.dev (on/off)',
86
87
  // 2.0 — surfaced for `/` autocomplete; documented in /help too.
87
88
  'compact': 'Summarize older messages to free up context',
88
89
  'commands': 'List custom slash commands in .codeep/commands/*.md',
@@ -5,7 +5,7 @@
5
5
  * decoupled from global state. Import-heavy commands use dynamic imports
6
6
  * to keep startup time low.
7
7
  */
8
- import { config, getCurrentProvider, getModelsForCurrentProvider, PROTOCOLS, LANGUAGES, setProvider, setApiKey, clearApiKey, getApiKey, isTelemetryEnabled, telemetryForcedOffByEnv, saveSession, startNewSession, loadSession, listSessionsWithInfo, deleteSession, renameSession, setProjectPermission, saveProfile, loadProfile, applyProfile, listProfiles, deleteProfile, initializeAsProject, isManuallyInitializedProject, } from '../config/index.js';
8
+ import { config, getCurrentProvider, getModelsForCurrentProvider, PROTOCOLS, LANGUAGES, setProvider, setApiKey, clearApiKey, getApiKey, isTelemetryEnabled, telemetryForcedOffByEnv, isKeySyncEnabled, keySyncForcedOffByEnv, saveSession, startNewSession, loadSession, listSessionsWithInfo, deleteSession, renameSession, setProjectPermission, saveProfile, loadProfile, applyProfile, listProfiles, deleteProfile, initializeAsProject, isManuallyInitializedProject, } from '../config/index.js';
9
9
  import { getProjectContext } from '../utils/project.js';
10
10
  import { getCurrentVersion } from '../utils/update.js';
11
11
  import { getProviderList, getProvider } from '../config/providers.js';
@@ -274,6 +274,35 @@ export async function handleCommand(command, args, ctx) {
274
274
  ctx.app.addMessage({ role: 'system', content: tLines.join('\n') });
275
275
  break;
276
276
  }
277
+ case 'keysync': {
278
+ const sub = args[0]?.toLowerCase();
279
+ const envOff = keySyncForcedOffByEnv();
280
+ if (sub === 'on' || sub === 'off') {
281
+ if (envOff) {
282
+ ctx.app.notify('Cloud key sync is forced OFF by CODEEP_NO_KEY_SYNC — unset that env var to change it.');
283
+ break;
284
+ }
285
+ config.set('syncKeysToCloud', sub === 'on');
286
+ ctx.app.notify(sub === 'on'
287
+ ? 'Cloud key sync on — `codeep account push/sync` will now upload/download API keys. Note: synced keys are stored server-readable on codeep.dev.'
288
+ : 'Cloud key sync off — API keys stay in your OS keychain only. (Run `codeep account purge-keys` to also wipe any keys already on the server.)');
289
+ break;
290
+ }
291
+ if (sub && sub !== 'status') {
292
+ ctx.app.notify('Usage: /keysync · /keysync on · /keysync off');
293
+ break;
294
+ }
295
+ const flag = config.get('syncKeysToCloud') === true;
296
+ const kLines = ['## Cloud key sync', ''];
297
+ kLines.push(`**State** ${isKeySyncEnabled() ? 'on' : 'off'}`);
298
+ kLines.push(`**Flag** syncKeysToCloud = ${flag}`);
299
+ if (envOff)
300
+ kLines.push('**Env** forced off by CODEEP_NO_KEY_SYNC (overrides the flag)');
301
+ kLines.push('');
302
+ kLines.push('OFF by default. API keys live only in your OS keychain unless you turn this on. When on, `codeep account push`/`sync` upload/download keys, which are stored **server-readable** on codeep.dev. Toggle with `/keysync on` or `/keysync off`; wipe server copies with `codeep account purge-keys`.');
303
+ ctx.app.addMessage({ role: 'system', content: kLines.join('\n') });
304
+ break;
305
+ }
277
306
  case 'grant': {
278
307
  setProjectPermission(ctx.projectPath, true, true);
279
308
  ctx.setHasWriteAccess(true);
@@ -250,6 +250,16 @@ export const SETTINGS = [
250
250
  { value: false, label: 'Off' },
251
251
  ],
252
252
  },
253
+ {
254
+ key: 'syncKeysToCloud',
255
+ label: 'Sync API Keys to Cloud (server-readable)',
256
+ getValue: () => config.get('syncKeysToCloud') === true,
257
+ type: 'select',
258
+ options: [
259
+ { value: false, label: 'Off (keychain only)' },
260
+ { value: true, label: 'On (push/sync to codeep.dev)' },
261
+ ],
262
+ },
253
263
  ];
254
264
  /**
255
265
  * Format value for display
@@ -404,8 +404,9 @@ Codeep - AI-powered coding assistant TUI
404
404
  Usage:
405
405
  codeep Start interactive chat
406
406
  codeep account Link CLI to your codeep.dev dashboard
407
- codeep account sync Pull keys + personalities + commands + profile from codeep.dev
408
- codeep account push Push local keys + personalities + commands + profile to codeep.dev
407
+ codeep account sync Pull personalities + commands + profile (+ keys if cloud key sync is on)
408
+ codeep account push Push personalities + commands + profile (+ keys if cloud key sync is on)
409
+ codeep account purge-keys Delete all your API keys stored on codeep.dev (cloud only; local keychain untouched)
409
410
  codeep acp Start ACP server (for Zed editor integration)
410
411
  codeep review Offline code review for CI (--json, --fail-on, --rules, --ai)
411
412
  codeep hook install Install a git pre-commit hook running \`codeep review\`
@@ -425,9 +426,7 @@ Commands (in chat):
425
426
  if (args[0] === 'account') {
426
427
  const sub = args[1];
427
428
  if (sub === 'sync' || sub === 'pull') {
428
- // Pull API keys from codeep.dev and save to local config
429
- const { pullKeys } = await import('../utils/codeepCloud.js');
430
- const { getSyncToken, setApiKey, loadAllApiKeys: loadKeys } = await import('../config/index.js');
429
+ const { getSyncToken, setApiKey, loadAllApiKeys: loadKeys, isKeySyncEnabled } = await import('../config/index.js');
431
430
  if (!getSyncToken()) {
432
431
  console.log('\n Not linked to codeep.dev. Run: codeep account\n');
433
432
  process.exit(1);
@@ -436,28 +435,36 @@ Commands (in chat):
436
435
  // key. Otherwise the first setApiKey flips keysSecured=true and any local
437
436
  // legacy plaintext keys would never migrate (orphaned, invisible).
438
437
  await loadKeys();
439
- process.stdout.write(' Pulling keys from codeep.dev...');
440
- const keys = await pullKeys();
441
- if (!keys) {
442
- console.log(' failed.\n Check your connection or re-link with: codeep account\n');
443
- process.exit(1);
444
- }
445
- const count = Object.keys(keys).length;
446
- if (count === 0) {
447
- console.log(' no keys found.\n Add keys at codeep.dev/dashboard');
448
- }
449
- else {
450
- let synced = 0;
451
- for (const [provider, key] of Object.entries(keys)) {
452
- try {
453
- await setApiKey(key, provider);
454
- synced++;
455
- }
456
- catch {
457
- console.log(`\n Warning: could not securely store the key for ${provider}.`);
438
+ // API keys are opt-in (default OFF). Pull them only when cloud key sync is
439
+ // enabled; the personal config below always syncs (no secrets).
440
+ if (isKeySyncEnabled()) {
441
+ const { pullKeys } = await import('../utils/codeepCloud.js');
442
+ process.stdout.write(' Pulling keys from codeep.dev...');
443
+ const keys = await pullKeys();
444
+ if (!keys) {
445
+ console.log(' failed.\n Check your connection or re-link with: codeep account\n');
446
+ process.exit(1);
447
+ }
448
+ const count = Object.keys(keys).length;
449
+ if (count === 0) {
450
+ console.log(' no keys found.\n Add keys at codeep.dev/dashboard');
451
+ }
452
+ else {
453
+ let synced = 0;
454
+ for (const [provider, key] of Object.entries(keys)) {
455
+ try {
456
+ await setApiKey(key, provider);
457
+ synced++;
458
+ }
459
+ catch {
460
+ console.log(`\n Warning: could not securely store the key for ${provider}.`);
461
+ }
458
462
  }
463
+ console.log(` synced ${synced} key${synced !== 1 ? 's' : ''}.`);
459
464
  }
460
- console.log(` synced ${synced} key${synced !== 1 ? 's' : ''}.`);
465
+ }
466
+ else {
467
+ console.log(' Cloud key sync is off — skipping API keys. Enable with: /keysync on');
461
468
  }
462
469
  // Also pull portable personal config — personalities + custom commands +
463
470
  // the user profile. Additive merge (never clobbers local files).
@@ -478,29 +485,38 @@ Commands (in chat):
478
485
  process.exit(0);
479
486
  }
480
487
  if (sub === 'push') {
481
- // Push local API keys to codeep.dev
482
- const { pushKeys } = await import('../utils/codeepCloud.js');
483
- const { getSyncToken, getApiKey } = await import('../config/index.js');
484
- const { PROVIDERS } = await import('../config/providers.js');
488
+ const { getSyncToken, getApiKey, isKeySyncEnabled } = await import('../config/index.js');
485
489
  if (!getSyncToken()) {
486
490
  console.log('\n Not linked to codeep.dev. Run: codeep account\n');
487
491
  process.exit(1);
488
492
  }
489
- await loadAllApiKeys();
490
- const keys = {};
491
- for (const providerId of Object.keys(PROVIDERS)) {
492
- const key = getApiKey(providerId);
493
- if (key)
494
- keys[providerId] = key;
495
- }
496
- const count = Object.keys(keys).length;
497
- if (count === 0) {
498
- console.log('\n No local API keys to push.\n');
499
- process.exit(0);
500
- }
501
- process.stdout.write(` Pushing ${count} key${count !== 1 ? 's' : ''} to codeep.dev...`);
502
- const ok = await pushKeys(keys);
503
- console.log(ok ? ' done.' : ' failed.');
493
+ // API keys are opt-in (default OFF). Push them only when cloud key sync is
494
+ // enabled; the personal config below always pushes (no secrets).
495
+ let keyPushFailed = false;
496
+ if (isKeySyncEnabled()) {
497
+ const { pushKeys } = await import('../utils/codeepCloud.js');
498
+ const { PROVIDERS } = await import('../config/providers.js');
499
+ await loadAllApiKeys();
500
+ const keys = {};
501
+ for (const providerId of Object.keys(PROVIDERS)) {
502
+ const key = getApiKey(providerId);
503
+ if (key)
504
+ keys[providerId] = key;
505
+ }
506
+ const count = Object.keys(keys).length;
507
+ if (count === 0) {
508
+ console.log(' No local API keys to push.');
509
+ }
510
+ else {
511
+ process.stdout.write(` Pushing ${count} key${count !== 1 ? 's' : ''} to codeep.dev...`);
512
+ const ok = await pushKeys(keys);
513
+ console.log(ok ? ' done.' : ' failed.');
514
+ keyPushFailed = !ok;
515
+ }
516
+ }
517
+ else {
518
+ console.log(' Cloud key sync is off — skipping API keys. Enable with: /keysync on');
519
+ }
504
520
  // Also push portable personal config — personalities + commands + profile.
505
521
  const { pushPersonalities, pushCommands, pushUserProfile } = await import('../utils/codeepCloud.js');
506
522
  const pCount = await pushPersonalities();
@@ -515,6 +531,19 @@ Commands (in chat):
515
531
  console.log(' Pushed your profile (about you).');
516
532
  }
517
533
  console.log('');
534
+ process.exit(keyPushFailed ? 1 : 0);
535
+ }
536
+ if (sub === 'purge-keys') {
537
+ const { getSyncToken } = await import('../config/index.js');
538
+ if (!getSyncToken()) {
539
+ console.log('\n Not linked to codeep.dev. Run: codeep account\n');
540
+ process.exit(1);
541
+ }
542
+ const { purgeKeys } = await import('../utils/codeepCloud.js');
543
+ process.stdout.write(' Deleting all your API keys from codeep.dev...');
544
+ const ok = await purgeKeys();
545
+ console.log(ok ? ' done. (Local keychain keys are untouched.)' : ' failed.');
546
+ console.log('');
518
547
  process.exit(ok ? 0 : 1);
519
548
  }
520
549
  const { runAccountFlow } = await import('../utils/codeepCloud.js');
@@ -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 };
@@ -65,6 +65,12 @@ export declare function pullKeys(): Promise<Record<string, string> | null>;
65
65
  * Returns true on success.
66
66
  */
67
67
  export declare function pushKeys(keys: Record<string, string>): Promise<boolean>;
68
+ /**
69
+ * Purge ALL of the user's API keys stored on codeep.dev (cloud-only — local
70
+ * keychain keys are untouched). A clean exit for anyone who synced keys and
71
+ * later wants them off the server. Returns true on success.
72
+ */
73
+ export declare function purgeKeys(): Promise<boolean>;
68
74
  export declare const pullPersonalities: () => Promise<number | null>;
69
75
  export declare const pushPersonalities: () => Promise<number | null>;
70
76
  export declare const pullCommands: () => Promise<number | null>;
@@ -221,6 +221,22 @@ export async function pushKeys(keys) {
221
221
  });
222
222
  return res?.ok ?? false;
223
223
  }
224
+ /**
225
+ * Purge ALL of the user's API keys stored on codeep.dev (cloud-only — local
226
+ * keychain keys are untouched). A clean exit for anyone who synced keys and
227
+ * later wants them off the server. Returns true on success.
228
+ */
229
+ export async function purgeKeys() {
230
+ const syncToken = getSyncToken();
231
+ if (!syncToken)
232
+ return false;
233
+ const res = await fetchWithRetry(`${API_BASE}/api/keys`, {
234
+ method: 'DELETE',
235
+ headers: { 'Content-Type': 'application/json', 'x-sync-token': syncToken },
236
+ body: JSON.stringify({ all: true }),
237
+ });
238
+ return res?.ok ?? false;
239
+ }
224
240
  // ─── Portable personal config sync (personalities + commands) ──────────────────
225
241
  //
226
242
  // Both are name → raw-.md-body bundles stored in a global dir
@@ -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.7.0";
1
+ export declare const VERSION = "2.9.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.7.0';
4
+ export const VERSION = '2.9.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "2.7.0",
3
+ "version": "2.9.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",