更新大量 tsx 原始文件; 已经迁移 login panel; 部分 (#121)

* style(B1-1): 格式化 ink/buddy/cli/context/screens/tasks/services/keybindings/state (43 files)

纯格式化:移除分号、React Compiler import、import 多行展开。
修复了 Box.tsx 和 ScrollBox.tsx 中无效的 global.d.ts import。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-2): 格式化 commands (79 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-3): 格式化 components/messages,permissions,mcp,sandbox,shell (104 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-4): 格式化 components/PromptInput,FeedbackSurvey,tasks,agents,skills,design-system,wizard (73 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-5): 格式化 components其余 + hooks + tools (232 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(B1-6): 格式化 main/entrypoints/utils/moreright (21 files)

纯格式化:移除分号、React Compiler import、import 多行展开。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: 更新 README,新增 Run.ps1/TODO.md,删除 V6.md

- README.md: 大幅重写,更详细版本历史和配置示例
- Run.ps1: 新增 Windows 启动脚本
- TODO.md: 新增包完成清单
- V6.md: 删除(架构重构规划已不适用)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: 修复以前的问题

* fix: 修复 login 面板的问题

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-04-04 23:24:27 +08:00
committed by GitHub
parent 02694918b5
commit 5b1a52b8e0
559 changed files with 103807 additions and 101817 deletions

View File

@@ -3,359 +3,453 @@
* These are dynamically imported only when the corresponding `claude mcp *` command runs.
*/
import { stat } from 'fs/promises';
import pMap from 'p-map';
import { cwd } from 'process';
import React from 'react';
import { MCPServerDesktopImportDialog } from '../../components/MCPServerDesktopImportDialog.js';
import { render } from '../../ink.js';
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js';
import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from '../../services/analytics/index.js';
import { clearMcpClientConfig, clearServerTokensFromLocalStorage, getMcpClientConfig, readClientSecret, saveMcpClientSecret } from '../../services/mcp/auth.js';
import { connectToServer, getMcpServerConnectionBatchSize } from '../../services/mcp/client.js';
import { addMcpConfig, getAllMcpConfigs, getMcpConfigByName, getMcpConfigsByScope, removeMcpConfig } from '../../services/mcp/config.js';
import type { ConfigScope, ScopedMcpServerConfig } from '../../services/mcp/types.js';
import { describeMcpConfigFilePath, ensureConfigScope, getScopeLabel } from '../../services/mcp/utils.js';
import { AppStateProvider } from '../../state/AppState.js';
import { getCurrentProjectConfig, getGlobalConfig, saveCurrentProjectConfig } from '../../utils/config.js';
import { isFsInaccessible } from '../../utils/errors.js';
import { gracefulShutdown } from '../../utils/gracefulShutdown.js';
import { safeParseJSON } from '../../utils/json.js';
import { getPlatform } from '../../utils/platform.js';
import { cliError, cliOk } from '../exit.js';
async function checkMcpServerHealth(name: string, server: ScopedMcpServerConfig): Promise<string> {
import { stat } from 'fs/promises'
import pMap from 'p-map'
import { cwd } from 'process'
import React from 'react'
import { MCPServerDesktopImportDialog } from '../../components/MCPServerDesktopImportDialog.js'
import { render } from '../../ink.js'
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js'
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
logEvent,
} from '../../services/analytics/index.js'
import {
clearMcpClientConfig,
clearServerTokensFromLocalStorage,
getMcpClientConfig,
readClientSecret,
saveMcpClientSecret,
} from '../../services/mcp/auth.js'
import {
connectToServer,
getMcpServerConnectionBatchSize,
} from '../../services/mcp/client.js'
import {
addMcpConfig,
getAllMcpConfigs,
getMcpConfigByName,
getMcpConfigsByScope,
removeMcpConfig,
} from '../../services/mcp/config.js'
import type {
ConfigScope,
ScopedMcpServerConfig,
} from '../../services/mcp/types.js'
import {
describeMcpConfigFilePath,
ensureConfigScope,
getScopeLabel,
} from '../../services/mcp/utils.js'
import { AppStateProvider } from '../../state/AppState.js'
import {
getCurrentProjectConfig,
getGlobalConfig,
saveCurrentProjectConfig,
} from '../../utils/config.js'
import { isFsInaccessible } from '../../utils/errors.js'
import { gracefulShutdown } from '../../utils/gracefulShutdown.js'
import { safeParseJSON } from '../../utils/json.js'
import { getPlatform } from '../../utils/platform.js'
import { cliError, cliOk } from '../exit.js'
async function checkMcpServerHealth(
name: string,
server: ScopedMcpServerConfig,
): Promise<string> {
try {
const result = await connectToServer(name, server);
const result = await connectToServer(name, server)
if (result.type === 'connected') {
return '✓ Connected';
return '✓ Connected'
} else if (result.type === 'needs-auth') {
return '! Needs authentication';
return '! Needs authentication'
} else {
return '✗ Failed to connect';
return '✗ Failed to connect'
}
} catch (_error) {
return '✗ Connection error';
return '✗ Connection error'
}
}
// mcp serve (lines 45124532)
export async function mcpServeHandler({
debug,
verbose
verbose,
}: {
debug?: boolean;
verbose?: boolean;
debug?: boolean
verbose?: boolean
}): Promise<void> {
const providedCwd = cwd();
logEvent('tengu_mcp_start', {});
const providedCwd = cwd()
logEvent('tengu_mcp_start', {})
try {
await stat(providedCwd);
await stat(providedCwd)
} catch (error) {
if (isFsInaccessible(error)) {
cliError(`Error: Directory ${providedCwd} does not exist`);
cliError(`Error: Directory ${providedCwd} does not exist`)
}
throw error;
throw error
}
try {
const {
setup
} = await import('../../setup.js');
await setup(providedCwd, 'default', false, false, undefined, false);
const {
startMCPServer
} = await import('../../entrypoints/mcp.js');
await startMCPServer(providedCwd, debug ?? false, verbose ?? false);
const { setup } = await import('../../setup.js')
await setup(providedCwd, 'default', false, false, undefined, false)
const { startMCPServer } = await import('../../entrypoints/mcp.js')
await startMCPServer(providedCwd, debug ?? false, verbose ?? false)
} catch (error) {
cliError(`Error: Failed to start MCP server: ${error}`);
cliError(`Error: Failed to start MCP server: ${error}`)
}
}
// mcp remove (lines 45454635)
export async function mcpRemoveHandler(name: string, options: {
scope?: string;
}): Promise<void> {
export async function mcpRemoveHandler(
name: string,
options: { scope?: string },
): Promise<void> {
// Look up config before removing so we can clean up secure storage
const serverBeforeRemoval = getMcpConfigByName(name);
const serverBeforeRemoval = getMcpConfigByName(name)
const cleanupSecureStorage = () => {
if (serverBeforeRemoval && (serverBeforeRemoval.type === 'sse' || serverBeforeRemoval.type === 'http')) {
clearServerTokensFromLocalStorage(name, serverBeforeRemoval);
clearMcpClientConfig(name, serverBeforeRemoval);
if (
serverBeforeRemoval &&
(serverBeforeRemoval.type === 'sse' ||
serverBeforeRemoval.type === 'http')
) {
clearServerTokensFromLocalStorage(name, serverBeforeRemoval)
clearMcpClientConfig(name, serverBeforeRemoval)
}
};
}
try {
if (options.scope) {
const scope = ensureConfigScope(options.scope);
const scope = ensureConfigScope(options.scope)
logEvent('tengu_mcp_delete', {
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
});
await removeMcpConfig(name, scope);
cleanupSecureStorage();
process.stdout.write(`Removed MCP server ${name} from ${scope} config\n`);
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`);
scope:
scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
await removeMcpConfig(name, scope)
cleanupSecureStorage()
process.stdout.write(`Removed MCP server ${name} from ${scope} config\n`)
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`)
}
// If no scope specified, check where the server exists
const projectConfig = getCurrentProjectConfig();
const globalConfig = getGlobalConfig();
const projectConfig = getCurrentProjectConfig()
const globalConfig = getGlobalConfig()
// Check if server exists in project scope (.mcp.json)
const {
servers: projectServers
} = getMcpConfigsByScope('project');
const mcpJsonExists = !!projectServers[name];
const { servers: projectServers } = getMcpConfigsByScope('project')
const mcpJsonExists = !!projectServers[name]
// Count how many scopes contain this server
const scopes: Array<Exclude<ConfigScope, 'dynamic'>> = [];
if (projectConfig.mcpServers?.[name]) scopes.push('local');
if (mcpJsonExists) scopes.push('project');
if (globalConfig.mcpServers?.[name]) scopes.push('user');
const scopes: Array<Exclude<ConfigScope, 'dynamic'>> = []
if (projectConfig.mcpServers?.[name]) scopes.push('local')
if (mcpJsonExists) scopes.push('project')
if (globalConfig.mcpServers?.[name]) scopes.push('user')
if (scopes.length === 0) {
cliError(`No MCP server found with name: "${name}"`);
cliError(`No MCP server found with name: "${name}"`)
} else if (scopes.length === 1) {
// Server exists in only one scope, remove it
const scope = scopes[0]!;
const scope = scopes[0]!
logEvent('tengu_mcp_delete', {
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
});
await removeMcpConfig(name, scope);
cleanupSecureStorage();
process.stdout.write(`Removed MCP server "${name}" from ${scope} config\n`);
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`);
scope:
scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
await removeMcpConfig(name, scope)
cleanupSecureStorage()
process.stdout.write(
`Removed MCP server "${name}" from ${scope} config\n`,
)
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`)
} else {
// Server exists in multiple scopes
process.stderr.write(`MCP server "${name}" exists in multiple scopes:\n`);
process.stderr.write(`MCP server "${name}" exists in multiple scopes:\n`)
scopes.forEach(scope => {
process.stderr.write(` - ${getScopeLabel(scope)} (${describeMcpConfigFilePath(scope)})\n`);
});
process.stderr.write('\nTo remove from a specific scope, use:\n');
process.stderr.write(
` - ${getScopeLabel(scope)} (${describeMcpConfigFilePath(scope)})\n`,
)
})
process.stderr.write('\nTo remove from a specific scope, use:\n')
scopes.forEach(scope => {
process.stderr.write(` claude mcp remove "${name}" -s ${scope}\n`);
});
cliError();
process.stderr.write(` claude mcp remove "${name}" -s ${scope}\n`)
})
cliError()
}
} catch (error) {
cliError((error as Error).message);
cliError((error as Error).message)
}
}
// mcp list (lines 46414688)
export async function mcpListHandler(): Promise<void> {
logEvent('tengu_mcp_list', {});
const {
servers: configs
} = await getAllMcpConfigs();
logEvent('tengu_mcp_list', {})
const { servers: configs } = await getAllMcpConfigs()
if (Object.keys(configs).length === 0) {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log('No MCP servers configured. Use `claude mcp add` to add a server.');
console.log(
'No MCP servers configured. Use `claude mcp add` to add a server.',
)
} else {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log('Checking MCP server health...\n');
console.log('Checking MCP server health...\n')
// Check servers concurrently
const entries = Object.entries(configs);
const results = await pMap(entries, async ([name, server]) => ({
name,
server,
status: await checkMcpServerHealth(name, server)
}), {
concurrency: getMcpServerConnectionBatchSize()
});
for (const {
name,
server,
status
} of results) {
const entries = Object.entries(configs)
const results = await pMap(
entries,
async ([name, server]) => ({
name,
server,
status: await checkMcpServerHealth(name, server),
}),
{ concurrency: getMcpServerConnectionBatchSize() },
)
for (const { name, server, status } of results) {
// Intentionally excluding sse-ide servers here since they're internal
if (server.type === 'sse') {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${name}: ${server.url} (SSE) - ${status}`);
console.log(`${name}: ${server.url} (SSE) - ${status}`)
} else if (server.type === 'http') {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${name}: ${server.url} (HTTP) - ${status}`);
console.log(`${name}: ${server.url} (HTTP) - ${status}`)
} else if (server.type === 'claudeai-proxy') {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${name}: ${server.url} - ${status}`);
console.log(`${name}: ${server.url} - ${status}`)
} else if (!server.type || server.type === 'stdio') {
const args = Array.isArray((server as any).args) ? (server as any).args : [];
const args = Array.isArray(server.args) ? server.args : []
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${name}: ${(server as any).command} ${args.join(' ')} - ${status}`);
console.log(`${name}: ${server.command} ${args.join(' ')} - ${status}`)
}
}
}
// Use gracefulShutdown to properly clean up MCP server connections
// (process.exit bypasses cleanup handlers, leaving child processes orphaned)
await gracefulShutdown(0);
await gracefulShutdown(0)
}
// mcp get (lines 46944786)
export async function mcpGetHandler(name: string): Promise<void> {
logEvent('tengu_mcp_get', {
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
});
const server = getMcpConfigByName(name);
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
const server = getMcpConfigByName(name)
if (!server) {
cliError(`No MCP server found with name: ${name}`);
cliError(`No MCP server found with name: ${name}`)
}
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${name}:`);
console.log(`${name}:`)
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` Scope: ${getScopeLabel(server.scope)}`);
console.log(` Scope: ${getScopeLabel(server.scope)}`)
// Check server health
const status = await checkMcpServerHealth(name, server);
const status = await checkMcpServerHealth(name, server)
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` Status: ${status}`);
console.log(` Status: ${status}`)
// Intentionally excluding sse-ide servers here since they're internal
if (server.type === 'sse') {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` Type: sse`);
console.log(` Type: sse`)
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` URL: ${server.url}`);
console.log(` URL: ${server.url}`)
if (server.headers) {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(' Headers:');
console.log(' Headers:')
for (const [key, value] of Object.entries(server.headers)) {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` ${key}: ${value}`);
console.log(` ${key}: ${value}`)
}
}
if (server.oauth?.clientId || server.oauth?.callbackPort) {
const parts: string[] = [];
const parts: string[] = []
if (server.oauth.clientId) {
parts.push('client_id configured');
const clientConfig = getMcpClientConfig(name, server);
if (clientConfig?.clientSecret) parts.push('client_secret configured');
parts.push('client_id configured')
const clientConfig = getMcpClientConfig(name, server)
if (clientConfig?.clientSecret) parts.push('client_secret configured')
}
if (server.oauth.callbackPort) parts.push(`callback_port ${server.oauth.callbackPort}`);
if (server.oauth.callbackPort)
parts.push(`callback_port ${server.oauth.callbackPort}`)
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` OAuth: ${parts.join(', ')}`);
console.log(` OAuth: ${parts.join(', ')}`)
}
} else if (server.type === 'http') {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` Type: http`);
console.log(` Type: http`)
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` URL: ${server.url}`);
console.log(` URL: ${server.url}`)
if (server.headers) {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(' Headers:');
console.log(' Headers:')
for (const [key, value] of Object.entries(server.headers)) {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` ${key}: ${value}`);
console.log(` ${key}: ${value}`)
}
}
if (server.oauth?.clientId || server.oauth?.callbackPort) {
const parts: string[] = [];
const parts: string[] = []
if (server.oauth.clientId) {
parts.push('client_id configured');
const clientConfig = getMcpClientConfig(name, server);
if (clientConfig?.clientSecret) parts.push('client_secret configured');
parts.push('client_id configured')
const clientConfig = getMcpClientConfig(name, server)
if (clientConfig?.clientSecret) parts.push('client_secret configured')
}
if (server.oauth.callbackPort) parts.push(`callback_port ${server.oauth.callbackPort}`);
if (server.oauth.callbackPort)
parts.push(`callback_port ${server.oauth.callbackPort}`)
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` OAuth: ${parts.join(', ')}`);
console.log(` OAuth: ${parts.join(', ')}`)
}
} else if (server.type === 'stdio') {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` Type: stdio`);
console.log(` Type: stdio`)
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` Command: ${server.command}`);
const args = Array.isArray(server.args) ? server.args : [];
console.log(` Command: ${server.command}`)
const args = Array.isArray(server.args) ? server.args : []
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` Args: ${args.join(' ')}`);
console.log(` Args: ${args.join(' ')}`)
if (server.env) {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(' Environment:');
console.log(' Environment:')
for (const [key, value] of Object.entries(server.env)) {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(` ${key}=${value}`);
console.log(` ${key}=${value}`)
}
}
}
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`\nTo remove this server, run: claude mcp remove "${name}" -s ${server.scope}`);
console.log(
`\nTo remove this server, run: claude mcp remove "${name}" -s ${server.scope}`,
)
// Use gracefulShutdown to properly clean up MCP server connections
// (process.exit bypasses cleanup handlers, leaving child processes orphaned)
await gracefulShutdown(0);
await gracefulShutdown(0)
}
// mcp add-json (lines 48014870)
export async function mcpAddJsonHandler(name: string, json: string, options: {
scope?: string;
clientSecret?: true;
}): Promise<void> {
export async function mcpAddJsonHandler(
name: string,
json: string,
options: { scope?: string; clientSecret?: true },
): Promise<void> {
try {
const scope = ensureConfigScope(options.scope);
const parsedJson = safeParseJSON(json);
const scope = ensureConfigScope(options.scope)
const parsedJson = safeParseJSON(json)
// Read secret before writing config so cancellation doesn't leave partial state
const needsSecret = options.clientSecret && parsedJson && typeof parsedJson === 'object' && 'type' in parsedJson && (parsedJson.type === 'sse' || parsedJson.type === 'http') && 'url' in parsedJson && typeof parsedJson.url === 'string' && 'oauth' in parsedJson && parsedJson.oauth && typeof parsedJson.oauth === 'object' && 'clientId' in parsedJson.oauth;
const clientSecret = needsSecret ? await readClientSecret() : undefined;
await addMcpConfig(name, parsedJson, scope);
const transportType = parsedJson && typeof parsedJson === 'object' && 'type' in parsedJson ? String(parsedJson.type || 'stdio') : 'stdio';
if (clientSecret && parsedJson && typeof parsedJson === 'object' && 'type' in parsedJson && (parsedJson.type === 'sse' || parsedJson.type === 'http') && 'url' in parsedJson && typeof parsedJson.url === 'string') {
saveMcpClientSecret(name, {
type: parsedJson.type,
url: parsedJson.url
}, clientSecret);
const needsSecret =
options.clientSecret &&
parsedJson &&
typeof parsedJson === 'object' &&
'type' in parsedJson &&
(parsedJson.type === 'sse' || parsedJson.type === 'http') &&
'url' in parsedJson &&
typeof parsedJson.url === 'string' &&
'oauth' in parsedJson &&
parsedJson.oauth &&
typeof parsedJson.oauth === 'object' &&
'clientId' in parsedJson.oauth
const clientSecret = needsSecret ? await readClientSecret() : undefined
await addMcpConfig(name, parsedJson, scope)
const transportType =
parsedJson && typeof parsedJson === 'object' && 'type' in parsedJson
? String(parsedJson.type || 'stdio')
: 'stdio'
if (
clientSecret &&
parsedJson &&
typeof parsedJson === 'object' &&
'type' in parsedJson &&
(parsedJson.type === 'sse' || parsedJson.type === 'http') &&
'url' in parsedJson &&
typeof parsedJson.url === 'string'
) {
saveMcpClientSecret(
name,
{ type: parsedJson.type, url: parsedJson.url },
clientSecret,
)
}
logEvent('tengu_mcp_add', {
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
source: 'json' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
type: transportType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
});
cliOk(`Added ${transportType} MCP server ${name} to ${scope} config`);
scope:
scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
source:
'json' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
type: transportType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
cliOk(`Added ${transportType} MCP server ${name} to ${scope} config`)
} catch (error) {
cliError((error as Error).message);
cliError((error as Error).message)
}
}
// mcp add-from-claude-desktop (lines 48814927)
export async function mcpAddFromDesktopHandler(options: {
scope?: string;
scope?: string
}): Promise<void> {
try {
const scope = ensureConfigScope(options.scope);
const platform = getPlatform();
const scope = ensureConfigScope(options.scope)
const platform = getPlatform()
logEvent('tengu_mcp_add', {
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
platform: platform as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
source: 'desktop' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
});
const {
readClaudeDesktopMcpServers
} = await import('../../utils/claudeDesktop.js');
const servers = await readClaudeDesktopMcpServers();
scope:
scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
platform:
platform as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
source:
'desktop' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
})
const { readClaudeDesktopMcpServers } = await import(
'../../utils/claudeDesktop.js'
)
const servers = await readClaudeDesktopMcpServers()
if (Object.keys(servers).length === 0) {
cliOk('No MCP servers found in Claude Desktop configuration or configuration file does not exist.');
cliOk(
'No MCP servers found in Claude Desktop configuration or configuration file does not exist.',
)
}
const {
unmount
} = await render(<AppStateProvider>
const { unmount } = await render(
<AppStateProvider>
<KeybindingSetup>
<MCPServerDesktopImportDialog servers={servers} scope={scope} onDone={() => {
unmount();
}} />
<MCPServerDesktopImportDialog
servers={servers}
scope={scope}
onDone={() => {
unmount()
}}
/>
</KeybindingSetup>
</AppStateProvider>, {
exitOnCtrlC: true
});
</AppStateProvider>,
{ exitOnCtrlC: true },
)
} catch (error) {
cliError((error as Error).message);
cliError((error as Error).message)
}
}
// mcp reset-project-choices (lines 49354952)
export async function mcpResetChoicesHandler(): Promise<void> {
logEvent('tengu_mcp_reset_mcpjson_choices', {});
logEvent('tengu_mcp_reset_mcpjson_choices', {})
saveCurrentProjectConfig(current => ({
...current,
enabledMcpjsonServers: [],
disabledMcpjsonServers: [],
enableAllProjectMcpServers: false
}));
cliOk('All project-scoped (.mcp.json) server approvals and rejections have been reset.\n' + 'You will be prompted for approval next time you start Claude Code.');
enableAllProjectMcpServers: false,
}))
cliOk(
'All project-scoped (.mcp.json) server approvals and rejections have been reset.\n' +
'You will be prompted for approval next time you start Claude Code.',
)
}

View File

@@ -1,34 +1,37 @@
import { c as _c } from "react/compiler-runtime";
/**
* Miscellaneous subcommand handlers — extracted from main.tsx for lazy loading.
* setup-token, doctor, install
*/
/* eslint-disable custom-rules/no-process-exit -- CLI subcommand handlers intentionally exit */
import { cwd } from 'process';
import React from 'react';
import { WelcomeV2 } from '../../components/LogoV2/WelcomeV2.js';
import { useManagePlugins } from '../../hooks/useManagePlugins.js';
import type { Root } from '../../ink.js';
import { Box, Text } from '../../ink.js';
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js';
import { logEvent } from '../../services/analytics/index.js';
import { MCPConnectionManager } from '../../services/mcp/MCPConnectionManager.js';
import { AppStateProvider } from '../../state/AppState.js';
import { onChangeAppState } from '../../state/onChangeAppState.js';
import { isAnthropicAuthEnabled } from '../../utils/auth.js';
import { cwd } from 'process'
import React from 'react'
import { WelcomeV2 } from '../../components/LogoV2/WelcomeV2.js'
import { useManagePlugins } from '../../hooks/useManagePlugins.js'
import type { Root } from '../../ink.js'
import { Box, Text } from '../../ink.js'
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js'
import { logEvent } from '../../services/analytics/index.js'
import { MCPConnectionManager } from '../../services/mcp/MCPConnectionManager.js'
import { AppStateProvider } from '../../state/AppState.js'
import { onChangeAppState } from '../../state/onChangeAppState.js'
import { isAnthropicAuthEnabled } from '../../utils/auth.js'
export async function setupTokenHandler(root: Root): Promise<void> {
logEvent('tengu_setup_token_command', {});
const showAuthWarning = !isAnthropicAuthEnabled();
const {
ConsoleOAuthFlow
} = await import('../../components/ConsoleOAuthFlow.js');
logEvent('tengu_setup_token_command', {})
const showAuthWarning = !isAnthropicAuthEnabled()
const { ConsoleOAuthFlow } = await import(
'../../components/ConsoleOAuthFlow.js'
)
await new Promise<void>(resolve => {
root.render(<AppStateProvider onChangeAppState={onChangeAppState}>
root.render(
<AppStateProvider onChangeAppState={onChangeAppState}>
<KeybindingSetup>
<Box flexDirection="column" gap={1}>
<WelcomeV2 />
{showAuthWarning && <Box flexDirection="column">
{showAuthWarning && (
<Box flexDirection="column">
<Text color="warning">
Warning: You already have authentication configured via
environment variable or API key helper.
@@ -37,73 +40,87 @@ export async function setupTokenHandler(root: Root): Promise<void> {
The setup-token command will create a new OAuth token which
you can use instead.
</Text>
</Box>}
<ConsoleOAuthFlow onDone={() => {
void resolve();
}} mode="setup-token" startingMessage="This will guide you through long-lived (1-year) auth token setup for your Claude account. Claude subscription required." />
</Box>
)}
<ConsoleOAuthFlow
onDone={() => {
void resolve()
}}
mode="setup-token"
startingMessage="This will guide you through long-lived (1-year) auth token setup for your Claude account. Claude subscription required."
/>
</Box>
</KeybindingSetup>
</AppStateProvider>);
});
root.unmount();
process.exit(0);
</AppStateProvider>,
)
})
root.unmount()
process.exit(0)
}
// DoctorWithPlugins wrapper + doctor handler
const DoctorLazy = React.lazy(() => import('../../screens/Doctor.js').then(m => ({
default: m.Doctor
})));
function DoctorWithPlugins(t0) {
const $ = _c(2);
const {
onDone
} = t0;
useManagePlugins();
let t1;
if ($[0] !== onDone) {
t1 = <React.Suspense fallback={null}><DoctorLazy onDone={onDone} /></React.Suspense>;
$[0] = onDone;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
const DoctorLazy = React.lazy(() =>
import('../../screens/Doctor.js').then(m => ({ default: m.Doctor })),
)
function DoctorWithPlugins({
onDone,
}: {
onDone: () => void
}): React.ReactNode {
useManagePlugins()
return (
<React.Suspense fallback={null}>
<DoctorLazy onDone={onDone} />
</React.Suspense>
)
}
export async function doctorHandler(root: Root): Promise<void> {
logEvent('tengu_doctor_command', {});
logEvent('tengu_doctor_command', {})
await new Promise<void>(resolve => {
root.render(<AppStateProvider>
root.render(
<AppStateProvider>
<KeybindingSetup>
<MCPConnectionManager dynamicMcpConfig={undefined} isStrictMcpConfig={false}>
<DoctorWithPlugins onDone={() => {
void resolve();
}} />
<MCPConnectionManager
dynamicMcpConfig={undefined}
isStrictMcpConfig={false}
>
<DoctorWithPlugins
onDone={() => {
void resolve()
}}
/>
</MCPConnectionManager>
</KeybindingSetup>
</AppStateProvider>);
});
root.unmount();
process.exit(0);
</AppStateProvider>,
)
})
root.unmount()
process.exit(0)
}
// install handler
export async function installHandler(target: string | undefined, options: {
force?: boolean;
}): Promise<void> {
const {
setup
} = await import('../../setup.js');
await setup(cwd(), 'default', false, false, undefined, false);
const {
install
} = await import('../../commands/install.js');
export async function installHandler(
target: string | undefined,
options: { force?: boolean },
): Promise<void> {
const { setup } = await import('../../setup.js')
await setup(cwd(), 'default', false, false, undefined, false)
const { install } = await import('../../commands/install.js')
await new Promise<void>(resolve => {
const args: string[] = [];
if (target) args.push(target);
if (options.force) args.push('--force');
void install.call(result => {
void resolve();
process.exit(result.includes('failed') ? 1 : 0);
}, {}, args);
});
const args: string[] = []
if (target) args.push(target)
if (options.force) args.push('--force')
void install.call(
result => {
void resolve()
process.exit(result.includes('failed') ? 1 : 0)
},
{},
args,
)
})
}