init claude-code

This commit is contained in:
2026-04-01 17:32:37 +02:00
commit 73b208c009
1902 changed files with 513237 additions and 0 deletions
@@ -0,0 +1,61 @@
import { logEvent } from 'src/services/analytics/index.js'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js'
import { logError } from '../utils/log.js'
import {
getSettingsForSource,
updateSettingsForSource,
} from '../utils/settings/settings.js'
/**
* Migration: Move user-set autoUpdates preference to settings.json env var
* Only migrates if user explicitly disabled auto-updates (not for protection)
* This preserves user intent while allowing native installations to auto-update
*/
export function migrateAutoUpdatesToSettings(): void {
const globalConfig = getGlobalConfig()
// Only migrate if autoUpdates was explicitly set to false by user preference
// (not automatically for native protection)
if (
globalConfig.autoUpdates !== false ||
globalConfig.autoUpdatesProtectedForNative === true
) {
return
}
try {
const userSettings = getSettingsForSource('userSettings') || {}
// Always set DISABLE_AUTOUPDATER to preserve user intent
// We need to overwrite even if it exists, to ensure the migration is complete
updateSettingsForSource('userSettings', {
...userSettings,
env: {
...userSettings.env,
DISABLE_AUTOUPDATER: '1',
},
})
logEvent('tengu_migrate_autoupdates_to_settings', {
was_user_preference: true,
already_had_env_var: !!userSettings.env?.DISABLE_AUTOUPDATER,
})
// explicitly set, so this takes effect immediately
process.env.DISABLE_AUTOUPDATER = '1'
// Remove autoUpdates from global config after successful migration
saveGlobalConfig(current => {
const {
autoUpdates: _,
autoUpdatesProtectedForNative: __,
...updatedConfig
} = current
return updatedConfig
})
} catch (error) {
logError(new Error(`Failed to migrate auto-updates: ${error}`))
logEvent('tengu_migrate_autoupdates_error', {
has_error: true,
})
}
}
@@ -0,0 +1,40 @@
import { logEvent } from 'src/services/analytics/index.js'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js'
import { logError } from '../utils/log.js'
import {
hasSkipDangerousModePermissionPrompt,
updateSettingsForSource,
} from '../utils/settings/settings.js'
/**
* Migration: Move bypassPermissionsModeAccepted from global config to settings.json
* as skipDangerousModePermissionPrompt. This is a better home since settings.json
* is the user-configurable settings file.
*/
export function migrateBypassPermissionsAcceptedToSettings(): void {
const globalConfig = getGlobalConfig()
if (!globalConfig.bypassPermissionsModeAccepted) {
return
}
try {
if (!hasSkipDangerousModePermissionPrompt()) {
updateSettingsForSource('userSettings', {
skipDangerousModePermissionPrompt: true,
})
}
logEvent('tengu_migrate_bypass_permissions_accepted', {})
saveGlobalConfig(current => {
if (!('bypassPermissionsModeAccepted' in current)) return current
const { bypassPermissionsModeAccepted: _, ...updatedConfig } = current
return updatedConfig
})
} catch (error) {
logError(
new Error(`Failed to migrate bypass permissions accepted: ${error}`),
)
}
}
@@ -0,0 +1,118 @@
import { logEvent } from 'src/services/analytics/index.js'
import {
getCurrentProjectConfig,
saveCurrentProjectConfig,
} from '../utils/config.js'
import { logError } from '../utils/log.js'
import {
getSettingsForSource,
updateSettingsForSource,
} from '../utils/settings/settings.js'
/**
* Migration: Move MCP server approval fields from project config to local settings
* This migrates both enableAllProjectMcpServers and enabledMcpjsonServers to the
* settings system for better management and consistency.
*/
export function migrateEnableAllProjectMcpServersToSettings(): void {
const projectConfig = getCurrentProjectConfig()
// Check if any field exists in project config
const hasEnableAll = projectConfig.enableAllProjectMcpServers !== undefined
const hasEnabledServers =
projectConfig.enabledMcpjsonServers &&
projectConfig.enabledMcpjsonServers.length > 0
const hasDisabledServers =
projectConfig.disabledMcpjsonServers &&
projectConfig.disabledMcpjsonServers.length > 0
if (!hasEnableAll && !hasEnabledServers && !hasDisabledServers) {
return
}
try {
const existingSettings = getSettingsForSource('localSettings') || {}
const updates: Partial<{
enableAllProjectMcpServers: boolean
enabledMcpjsonServers: string[]
disabledMcpjsonServers: string[]
}> = {}
const fieldsToRemove: Array<
| 'enableAllProjectMcpServers'
| 'enabledMcpjsonServers'
| 'disabledMcpjsonServers'
> = []
// Migrate enableAllProjectMcpServers if it exists and hasn't been migrated
if (
hasEnableAll &&
existingSettings.enableAllProjectMcpServers === undefined
) {
updates.enableAllProjectMcpServers =
projectConfig.enableAllProjectMcpServers
fieldsToRemove.push('enableAllProjectMcpServers')
} else if (hasEnableAll) {
// Already migrated, just mark for removal
fieldsToRemove.push('enableAllProjectMcpServers')
}
// Migrate enabledMcpjsonServers if it exists
if (hasEnabledServers && projectConfig.enabledMcpjsonServers) {
const existingEnabledServers =
existingSettings.enabledMcpjsonServers || []
// Merge the servers (avoiding duplicates)
updates.enabledMcpjsonServers = [
...new Set([
...existingEnabledServers,
...projectConfig.enabledMcpjsonServers,
]),
]
fieldsToRemove.push('enabledMcpjsonServers')
}
// Migrate disabledMcpjsonServers if it exists
if (hasDisabledServers && projectConfig.disabledMcpjsonServers) {
const existingDisabledServers =
existingSettings.disabledMcpjsonServers || []
// Merge the servers (avoiding duplicates)
updates.disabledMcpjsonServers = [
...new Set([
...existingDisabledServers,
...projectConfig.disabledMcpjsonServers,
]),
]
fieldsToRemove.push('disabledMcpjsonServers')
}
// Update settings if there are any updates
if (Object.keys(updates).length > 0) {
updateSettingsForSource('localSettings', updates)
}
// Remove migrated fields from project config
if (
fieldsToRemove.includes('enableAllProjectMcpServers') ||
fieldsToRemove.includes('enabledMcpjsonServers') ||
fieldsToRemove.includes('disabledMcpjsonServers')
) {
saveCurrentProjectConfig(current => {
const {
enableAllProjectMcpServers: _enableAll,
enabledMcpjsonServers: _enabledServers,
disabledMcpjsonServers: _disabledServers,
...configWithoutFields
} = current
return configWithoutFields
})
}
// Log the migration event
logEvent('tengu_migrate_mcp_approval_fields_success', {
migratedCount: fieldsToRemove.length,
})
} catch (e: unknown) {
// Log migration failure but don't throw to avoid breaking startup
logError(e)
logEvent('tengu_migrate_mcp_approval_fields_error', {})
}
}
+45
View File
@@ -0,0 +1,45 @@
import {
getSettingsForSource,
updateSettingsForSource,
} from '../utils/settings/settings.js'
/**
* Migrate users on removed fennec model aliases to their new Opus 4.6 aliases.
* - fennec-latest → opus
* - fennec-latest[1m] → opus[1m]
* - fennec-fast-latest → opus[1m] + fast mode
* - opus-4-5-fast → opus + fast mode
*
* Only touches userSettings. Reading and writing the same source keeps this
* idempotent without a completion flag. Fennec aliases in project/local/policy
* settings are left alone — we can't rewrite those, and reading merged
* settings here would cause infinite re-runs + silent global promotion.
*/
export function migrateFennecToOpus(): void {
if (process.env.USER_TYPE !== 'ant') {
return
}
const settings = getSettingsForSource('userSettings')
const model = settings?.model
if (typeof model === 'string') {
if (model.startsWith('fennec-latest[1m]')) {
updateSettingsForSource('userSettings', {
model: 'opus[1m]',
})
} else if (model.startsWith('fennec-latest')) {
updateSettingsForSource('userSettings', {
model: 'opus',
})
} else if (
model.startsWith('fennec-fast-latest') ||
model.startsWith('opus-4-5-fast')
) {
updateSettingsForSource('userSettings', {
model: 'opus[1m]',
fastMode: true,
})
}
}
}
+57
View File
@@ -0,0 +1,57 @@
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../services/analytics/index.js'
import { saveGlobalConfig } from '../utils/config.js'
import { isLegacyModelRemapEnabled } from '../utils/model/model.js'
import { getAPIProvider } from '../utils/model/providers.js'
import {
getSettingsForSource,
updateSettingsForSource,
} from '../utils/settings/settings.js'
/**
* Migrate first-party users off explicit Opus 4.0/4.1 model strings.
*
* The 'opus' alias already resolves to Opus 4.6 for 1P, so anyone still
* on an explicit 4.0/4.1 string pinned it in settings before 4.5 launched.
* parseUserSpecifiedModel now silently remaps these at runtime anyway —
* this migration cleans up the settings file so /model shows the right
* thing, and sets a timestamp so the REPL can show a one-time notification.
*
* Only touches userSettings. Legacy strings in project/local/policy settings
* are left alone (we can't/shouldn't rewrite those) and are still remapped at
* runtime by parseUserSpecifiedModel. Reading and writing the same source
* keeps this idempotent without a completion flag, and avoids silently
* promoting 'opus' to the global default for users who only pinned it in one
* project.
*/
export function migrateLegacyOpusToCurrent(): void {
if (getAPIProvider() !== 'firstParty') {
return
}
if (!isLegacyModelRemapEnabled()) {
return
}
const model = getSettingsForSource('userSettings')?.model
if (
model !== 'claude-opus-4-20250514' &&
model !== 'claude-opus-4-1-20250805' &&
model !== 'claude-opus-4-0' &&
model !== 'claude-opus-4-1'
) {
return
}
updateSettingsForSource('userSettings', { model: 'opus' })
saveGlobalConfig(current => ({
...current,
legacyOpusMigrationTimestamp: Date.now(),
}))
logEvent('tengu_legacy_opus_migration', {
from_model:
model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
}
+43
View File
@@ -0,0 +1,43 @@
import { logEvent } from '../services/analytics/index.js'
import {
getDefaultMainLoopModelSetting,
isOpus1mMergeEnabled,
parseUserSpecifiedModel,
} from '../utils/model/model.js'
import {
getSettingsForSource,
updateSettingsForSource,
} from '../utils/settings/settings.js'
/**
* Migrate users with 'opus' pinned in their settings to 'opus[1m]' when they
* are eligible for the merged Opus 1M experience (Max/Team Premium on 1P).
*
* CLI invocations with --model opus are unaffected: that flag is a runtime
* override and does not touch userSettings, so it continues to use plain Opus.
*
* Pro subscribers are skipped — they retain separate Opus and Opus 1M options.
* 3P users are skipped — their model strings are full model IDs, not aliases.
*
* Idempotent: only writes if userSettings.model is exactly 'opus'.
*/
export function migrateOpusToOpus1m(): void {
if (!isOpus1mMergeEnabled()) {
return
}
const model = getSettingsForSource('userSettings')?.model
if (model !== 'opus') {
return
}
const migrated = 'opus[1m]'
const modelToSet =
parseUserSpecifiedModel(migrated) ===
parseUserSpecifiedModel(getDefaultMainLoopModelSetting())
? undefined
: migrated
updateSettingsForSource('userSettings', { model: modelToSet })
logEvent('tengu_opus_to_opus1m_migration', {})
}
@@ -0,0 +1,22 @@
import { saveGlobalConfig } from '../utils/config.js'
/**
* Migrate the `replBridgeEnabled` config key to `remoteControlAtStartup`.
*
* The old key was an implementation detail that leaked into user-facing config.
* This migration copies the value to the new key and removes the old one.
* Idempotent — only acts when the old key exists and the new one doesn't.
*/
export function migrateReplBridgeEnabledToRemoteControlAtStartup(): void {
saveGlobalConfig(prev => {
// The old key is no longer in the GlobalConfig type, so access it via
// an untyped cast. Only migrate if the old key exists and the new key
// hasn't been set yet.
const oldValue = (prev as Record<string, unknown>)['replBridgeEnabled']
if (oldValue === undefined) return prev
if (prev.remoteControlAtStartup !== undefined) return prev
const next = { ...prev, remoteControlAtStartup: Boolean(oldValue) }
delete (next as Record<string, unknown>)['replBridgeEnabled']
return next
})
}
+48
View File
@@ -0,0 +1,48 @@
import {
getMainLoopModelOverride,
setMainLoopModelOverride,
} from '../bootstrap/state.js'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js'
import {
getSettingsForSource,
updateSettingsForSource,
} from '../utils/settings/settings.js'
/**
* Migrate users who had "sonnet[1m]" saved to the explicit "sonnet-4-5-20250929[1m]".
*
* The "sonnet" alias now resolves to Sonnet 4.6, so users who previously set
* "sonnet[1m]" (targeting Sonnet 4.5 with 1M context) need to be pinned to the
* explicit version to preserve their intended model.
*
* This is needed because Sonnet 4.6 1M was offered to a different group of users than
* Sonnet 4.5 1M, so we needed to pin existing sonnet[1m] users to Sonnet 4.5 1M.
*
* Reads from userSettings specifically (not merged settings) so we don't
* promote a project-scoped "sonnet[1m]" to the global default. Runs once,
* tracked by a completion flag in global config.
*/
export function migrateSonnet1mToSonnet45(): void {
const config = getGlobalConfig()
if (config.sonnet1m45MigrationComplete) {
return
}
const model = getSettingsForSource('userSettings')?.model
if (model === 'sonnet[1m]') {
updateSettingsForSource('userSettings', {
model: 'sonnet-4-5-20250929[1m]',
})
}
// Also migrate the in-memory override if already set
const override = getMainLoopModelOverride()
if (override === 'sonnet[1m]') {
setMainLoopModelOverride('sonnet-4-5-20250929[1m]')
}
saveGlobalConfig(current => ({
...current,
sonnet1m45MigrationComplete: true,
}))
}
+67
View File
@@ -0,0 +1,67 @@
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../services/analytics/index.js'
import {
isMaxSubscriber,
isProSubscriber,
isTeamPremiumSubscriber,
} from '../utils/auth.js'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js'
import { getAPIProvider } from '../utils/model/providers.js'
import {
getSettingsForSource,
updateSettingsForSource,
} from '../utils/settings/settings.js'
/**
* Migrate Pro/Max/Team Premium first-party users off explicit Sonnet 4.5
* model strings to the 'sonnet' alias (which now resolves to Sonnet 4.6).
*
* Users may have been pinned to explicit Sonnet 4.5 strings by:
* - The earlier migrateSonnet1mToSonnet45 migration (sonnet[1m] → explicit 4.5[1m])
* - Manually selecting it via /model
*
* Reads userSettings specifically (not merged) so we only migrate what /model
* wrote — project/local pins are left alone.
* Idempotent: only writes if userSettings.model matches a Sonnet 4.5 string.
*/
export function migrateSonnet45ToSonnet46(): void {
if (getAPIProvider() !== 'firstParty') {
return
}
if (!isProSubscriber() && !isMaxSubscriber() && !isTeamPremiumSubscriber()) {
return
}
const model = getSettingsForSource('userSettings')?.model
if (
model !== 'claude-sonnet-4-5-20250929' &&
model !== 'claude-sonnet-4-5-20250929[1m]' &&
model !== 'sonnet-4-5-20250929' &&
model !== 'sonnet-4-5-20250929[1m]'
) {
return
}
const has1m = model.endsWith('[1m]')
updateSettingsForSource('userSettings', {
model: has1m ? 'sonnet[1m]' : 'sonnet',
})
// Skip notification for brand-new users — they never experienced the old default
const config = getGlobalConfig()
if (config.numStartups > 1) {
saveGlobalConfig(current => ({
...current,
sonnet45To46MigrationTimestamp: Date.now(),
}))
}
logEvent('tengu_sonnet45_to_46_migration', {
from_model:
model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
has_1m: has1m,
})
}
@@ -0,0 +1,51 @@
import { feature } from 'bun:bundle'
import { logEvent } from 'src/services/analytics/index.js'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js'
import { logError } from '../utils/log.js'
import { getAutoModeEnabledState } from '../utils/permissions/permissionSetup.js'
import {
getSettingsForSource,
updateSettingsForSource,
} from '../utils/settings/settings.js'
/**
* One-shot migration: clear skipAutoPermissionPrompt for users who accepted
* the old 2-option AutoModeOptInDialog but don't have auto as their default.
* Re-surfaces the dialog so they see the new "make it my default mode" option.
* Guard lives in GlobalConfig (~/.claude.json), not settings.json, so it
* survives settings resets and doesn't re-arm itself.
*
* Only runs when tengu_auto_mode_config.enabled === 'enabled'. For 'opt-in'
* users, clearing skipAutoPermissionPrompt would remove auto from the carousel
* (permissionSetup.ts:988) — the dialog would become unreachable and the
* migration would defeat itself. In practice the ~40 target ants are all
* 'enabled' (they reached the old dialog via bare Shift+Tab, which requires
* 'enabled'), but the guard makes it safe regardless.
*/
export function resetAutoModeOptInForDefaultOffer(): void {
if (feature('TRANSCRIPT_CLASSIFIER')) {
const config = getGlobalConfig()
if (config.hasResetAutoModeOptInForDefaultOffer) return
if (getAutoModeEnabledState() !== 'enabled') return
try {
const user = getSettingsForSource('userSettings')
if (
user?.skipAutoPermissionPrompt &&
user?.permissions?.defaultMode !== 'auto'
) {
updateSettingsForSource('userSettings', {
skipAutoPermissionPrompt: undefined,
})
logEvent('tengu_migrate_reset_auto_opt_in_for_default_offer', {})
}
saveGlobalConfig(c => {
if (c.hasResetAutoModeOptInForDefaultOffer) return c
return { ...c, hasResetAutoModeOptInForDefaultOffer: true }
})
} catch (error) {
logError(new Error(`Failed to reset auto mode opt-in: ${error}`))
}
}
}
+51
View File
@@ -0,0 +1,51 @@
import { logEvent } from 'src/services/analytics/index.js'
import { isProSubscriber } from '../utils/auth.js'
import { getGlobalConfig, saveGlobalConfig } from '../utils/config.js'
import { getAPIProvider } from '../utils/model/providers.js'
import { getSettings_DEPRECATED } from '../utils/settings/settings.js'
export function resetProToOpusDefault(): void {
const config = getGlobalConfig()
if (config.opusProMigrationComplete) {
return
}
const apiProvider = getAPIProvider()
// Pro users on firstParty get auto-migrated to Opus 4.5 default
if (apiProvider !== 'firstParty' || !isProSubscriber()) {
saveGlobalConfig(current => ({
...current,
opusProMigrationComplete: true,
}))
logEvent('tengu_reset_pro_to_opus_default', { skipped: true })
return
}
const settings = getSettings_DEPRECATED()
// Only show notification if user was on default (no custom model setting)
if (settings?.model === undefined) {
const opusProMigrationTimestamp = Date.now()
saveGlobalConfig(current => ({
...current,
opusProMigrationComplete: true,
opusProMigrationTimestamp,
}))
logEvent('tengu_reset_pro_to_opus_default', {
skipped: false,
had_custom_model: false,
})
} else {
// User has a custom model setting, just mark migration complete
saveGlobalConfig(current => ({
...current,
opusProMigrationComplete: true,
}))
logEvent('tengu_reset_pro_to_opus_default', {
skipped: false,
had_custom_model: true,
})
}
}