mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 16:25:51 +00:00
chore: full Biome lint cleanup — zero errors, zero warnings
- Remove 203 unused biome-ignore suppression comments (noConsole rule is off) - Apply all FIXABLE transforms: Math.pow->**, parseInt radix, noUselessContinue, noUselessUndefinedInitialization, useIndexOf, useRegexLiterals, useShorthandFunctionType, noPrototypeBuiltins - Add targeted suppressions for 31 intentional patterns - Format all src/ files via Biome (quote style, import line width) - Result: 0 errors, 0 warnings across 2649 files
This commit is contained in:
@@ -41,7 +41,11 @@ import { type Tools, type ToolUseContext, toolMatchesName } from './Tool.js'
|
|||||||
import type { AgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
|
import type { AgentDefinition } from '@claude-code-best/builtin-tools/tools/AgentTool/loadAgentsDir.js'
|
||||||
import { SYNTHETIC_OUTPUT_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/SyntheticOutputTool/SyntheticOutputTool.js'
|
import { SYNTHETIC_OUTPUT_TOOL_NAME } from '@claude-code-best/builtin-tools/tools/SyntheticOutputTool/SyntheticOutputTool.js'
|
||||||
import type { APIError } from '@anthropic-ai/sdk'
|
import type { APIError } from '@anthropic-ai/sdk'
|
||||||
import type { CompactMetadata, Message, SystemCompactBoundaryMessage } from './types/message.js'
|
import type {
|
||||||
|
CompactMetadata,
|
||||||
|
Message,
|
||||||
|
SystemCompactBoundaryMessage,
|
||||||
|
} from './types/message.js'
|
||||||
import type { OrphanedPermission } from './types/textInputTypes.js'
|
import type { OrphanedPermission } from './types/textInputTypes.js'
|
||||||
import { createAbortController } from './utils/abortController.js'
|
import { createAbortController } from './utils/abortController.js'
|
||||||
import type { AttributionState } from './utils/commitAttribution.js'
|
import type { AttributionState } from './utils/commitAttribution.js'
|
||||||
@@ -708,7 +712,8 @@ export class QueryEngine {
|
|||||||
message.subtype === 'compact_boundary'
|
message.subtype === 'compact_boundary'
|
||||||
) {
|
) {
|
||||||
const compactMsg = message as SystemCompactBoundaryMessage
|
const compactMsg = message as SystemCompactBoundaryMessage
|
||||||
const tailUuid = compactMsg.compactMetadata?.preservedSegment?.tailUuid
|
const tailUuid =
|
||||||
|
compactMsg.compactMetadata?.preservedSegment?.tailUuid
|
||||||
if (tailUuid) {
|
if (tailUuid) {
|
||||||
const tailIdx = this.mutableMessages.findLastIndex(
|
const tailIdx = this.mutableMessages.findLastIndex(
|
||||||
m => m.uuid === tailUuid,
|
m => m.uuid === tailUuid,
|
||||||
@@ -768,7 +773,10 @@ export class QueryEngine {
|
|||||||
// streamed responses, this is null at content_block_stop time;
|
// streamed responses, this is null at content_block_stop time;
|
||||||
// the real value arrives via message_delta (handled below).
|
// the real value arrives via message_delta (handled below).
|
||||||
const msg = message as Message
|
const msg = message as Message
|
||||||
const stopReason = msg.message?.stop_reason as string | null | undefined
|
const stopReason = msg.message?.stop_reason as
|
||||||
|
| string
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
if (stopReason != null) {
|
if (stopReason != null) {
|
||||||
lastStopReason = stopReason
|
lastStopReason = stopReason
|
||||||
}
|
}
|
||||||
@@ -798,11 +806,15 @@ export class QueryEngine {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'stream_event': {
|
case 'stream_event': {
|
||||||
const event = (message as unknown as { event: Record<string, unknown> }).event
|
const event = (
|
||||||
|
message as unknown as { event: Record<string, unknown> }
|
||||||
|
).event
|
||||||
if (event.type === 'message_start') {
|
if (event.type === 'message_start') {
|
||||||
// Reset current message usage for new message
|
// Reset current message usage for new message
|
||||||
currentMessageUsage = EMPTY_USAGE
|
currentMessageUsage = EMPTY_USAGE
|
||||||
const eventMessage = event.message as { usage: BetaMessageDeltaUsage }
|
const eventMessage = event.message as {
|
||||||
|
usage: BetaMessageDeltaUsage
|
||||||
|
}
|
||||||
currentMessageUsage = updateUsage(
|
currentMessageUsage = updateUsage(
|
||||||
currentMessageUsage,
|
currentMessageUsage,
|
||||||
eventMessage.usage,
|
eventMessage.usage,
|
||||||
@@ -851,7 +863,15 @@ export class QueryEngine {
|
|||||||
void recordTranscript(messages)
|
void recordTranscript(messages)
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachment = msg.attachment as { type: string; data?: unknown; turnCount?: number; maxTurns?: number; prompt?: string; source_uuid?: string; [key: string]: unknown }
|
const attachment = msg.attachment as {
|
||||||
|
type: string
|
||||||
|
data?: unknown
|
||||||
|
turnCount?: number
|
||||||
|
maxTurns?: number
|
||||||
|
prompt?: string
|
||||||
|
source_uuid?: string
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
|
||||||
// Extract structured output from StructuredOutput tool calls
|
// Extract structured output from StructuredOutput tool calls
|
||||||
if (attachment.type === 'structured_output') {
|
if (attachment.type === 'structured_output') {
|
||||||
@@ -892,10 +912,7 @@ export class QueryEngine {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Yield queued_command attachments as SDK user message replays
|
// Yield queued_command attachments as SDK user message replays
|
||||||
else if (
|
else if (replayUserMessages && attachment.type === 'queued_command') {
|
||||||
replayUserMessages &&
|
|
||||||
attachment.type === 'queued_command'
|
|
||||||
) {
|
|
||||||
yield {
|
yield {
|
||||||
type: 'user',
|
type: 'user',
|
||||||
message: {
|
message: {
|
||||||
@@ -923,10 +940,7 @@ export class QueryEngine {
|
|||||||
// never shrinks (memory leak in long SDK sessions). The subtype
|
// never shrinks (memory leak in long SDK sessions). The subtype
|
||||||
// check lives inside the injected callback so feature-gated strings
|
// check lives inside the injected callback so feature-gated strings
|
||||||
// stay out of this file (excluded-strings check).
|
// stay out of this file (excluded-strings check).
|
||||||
const snipResult = this.config.snipReplay?.(
|
const snipResult = this.config.snipReplay?.(msg, this.mutableMessages)
|
||||||
msg,
|
|
||||||
this.mutableMessages,
|
|
||||||
)
|
|
||||||
if (snipResult !== undefined) {
|
if (snipResult !== undefined) {
|
||||||
if (snipResult.executed) {
|
if (snipResult.executed) {
|
||||||
this.mutableMessages.length = 0
|
this.mutableMessages.length = 0
|
||||||
@@ -936,10 +950,7 @@ export class QueryEngine {
|
|||||||
}
|
}
|
||||||
this.mutableMessages.push(msg)
|
this.mutableMessages.push(msg)
|
||||||
// Yield compact boundary messages to SDK
|
// Yield compact boundary messages to SDK
|
||||||
if (
|
if (msg.subtype === 'compact_boundary' && msg.compactMetadata) {
|
||||||
msg.subtype === 'compact_boundary' &&
|
|
||||||
msg.compactMetadata
|
|
||||||
) {
|
|
||||||
const compactMsg = msg as SystemCompactBoundaryMessage
|
const compactMsg = msg as SystemCompactBoundaryMessage
|
||||||
// Release pre-compaction messages for GC. The boundary was just
|
// Release pre-compaction messages for GC. The boundary was just
|
||||||
// pushed so it's the last element. query.ts already uses
|
// pushed so it's the last element. query.ts already uses
|
||||||
@@ -959,11 +970,18 @@ export class QueryEngine {
|
|||||||
subtype: 'compact_boundary' as const,
|
subtype: 'compact_boundary' as const,
|
||||||
session_id: getSessionId(),
|
session_id: getSessionId(),
|
||||||
uuid: msg.uuid,
|
uuid: msg.uuid,
|
||||||
compact_metadata: toSDKCompactMetadata(compactMsg.compactMetadata),
|
compact_metadata: toSDKCompactMetadata(
|
||||||
|
compactMsg.compactMetadata,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (msg.subtype === 'api_error') {
|
if (msg.subtype === 'api_error') {
|
||||||
const apiErrorMsg = msg as Message & { retryAttempt: number; maxRetries: number; retryInMs: number; error: APIError }
|
const apiErrorMsg = msg as Message & {
|
||||||
|
retryAttempt: number
|
||||||
|
maxRetries: number
|
||||||
|
retryInMs: number
|
||||||
|
error: APIError
|
||||||
|
}
|
||||||
yield {
|
yield {
|
||||||
type: 'system',
|
type: 'system',
|
||||||
subtype: 'api_retry' as const,
|
subtype: 'api_retry' as const,
|
||||||
@@ -980,7 +998,10 @@ export class QueryEngine {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'tool_use_summary': {
|
case 'tool_use_summary': {
|
||||||
const msg = message as Message & { summary: unknown; precedingToolUseIds: unknown }
|
const msg = message as Message & {
|
||||||
|
summary: unknown
|
||||||
|
precedingToolUseIds: unknown
|
||||||
|
}
|
||||||
// Yield tool use summary messages to SDK
|
// Yield tool use summary messages to SDK
|
||||||
yield {
|
yield {
|
||||||
type: 'tool_use_summary' as const,
|
type: 'tool_use_summary' as const,
|
||||||
@@ -1089,7 +1110,10 @@ export class QueryEngine {
|
|||||||
const edeResultType = result?.type ?? 'undefined'
|
const edeResultType = result?.type ?? 'undefined'
|
||||||
const edeLastContentType =
|
const edeLastContentType =
|
||||||
result?.type === 'assistant'
|
result?.type === 'assistant'
|
||||||
? (last(result.message!.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[])?.type ?? 'none')
|
? (last(
|
||||||
|
result.message!
|
||||||
|
.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[],
|
||||||
|
)?.type ?? 'none')
|
||||||
: 'n/a'
|
: 'n/a'
|
||||||
|
|
||||||
// Flush buffered transcript writes before yielding result.
|
// Flush buffered transcript writes before yielding result.
|
||||||
@@ -1147,7 +1171,10 @@ export class QueryEngine {
|
|||||||
let isApiError = false
|
let isApiError = false
|
||||||
|
|
||||||
if (result.type === 'assistant') {
|
if (result.type === 'assistant') {
|
||||||
const lastContent = last(result.message!.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[])
|
const lastContent = last(
|
||||||
|
result.message!
|
||||||
|
.content as import('@anthropic-ai/sdk/resources/beta/messages/messages.js').BetaContentBlock[],
|
||||||
|
)
|
||||||
if (
|
if (
|
||||||
lastContent?.type === 'text' &&
|
lastContent?.type === 'text' &&
|
||||||
!SYNTHETIC_MESSAGES.has(lastContent.text)
|
!SYNTHETIC_MESSAGES.has(lastContent.text)
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ import {
|
|||||||
setSystemPromptInjection,
|
setSystemPromptInjection,
|
||||||
} from '../context'
|
} from '../context'
|
||||||
import { clearMemoryFileCaches } from '../utils/claudemd'
|
import { clearMemoryFileCaches } from '../utils/claudemd'
|
||||||
import { cleanupTempDir, createTempDir, writeTempFile } from '../../tests/mocks/file-system'
|
import {
|
||||||
|
cleanupTempDir,
|
||||||
|
createTempDir,
|
||||||
|
writeTempFile,
|
||||||
|
} from '../../tests/mocks/file-system'
|
||||||
|
|
||||||
let tempDir = ''
|
let tempDir = ''
|
||||||
let projectClaudeMdContent = ''
|
let projectClaudeMdContent = ''
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type HookEvent = any;
|
export type HookEvent = any
|
||||||
export type ModelUsage = any;
|
export type ModelUsage = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type AgentColorName = any;
|
export type AgentColorName = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type HookCallbackMatcher = any;
|
export type HookCallbackMatcher = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type SessionId = any;
|
export type SessionId = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type randomUUID = any;
|
export type randomUUID = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type ModelSetting = any;
|
export type ModelSetting = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type ModelStrings = any;
|
export type ModelStrings = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type SettingSource = any;
|
export type SettingSource = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type resetSettingsCache = any;
|
export type resetSettingsCache = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type PluginHookMatcher = any;
|
export type PluginHookMatcher = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type createSignal = any;
|
export type createSignal = any
|
||||||
|
|||||||
@@ -1755,4 +1755,6 @@ export function getPromptId(): string | null {
|
|||||||
export function setPromptId(id: string | null): void {
|
export function setPromptId(id: string | null): void {
|
||||||
STATE.promptId = id
|
STATE.promptId = id
|
||||||
}
|
}
|
||||||
export function isReplBridgeActive(): boolean { return false; }
|
export function isReplBridgeActive(): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -225,7 +225,9 @@ export function createBridgeApiClient(deps: BridgeApiDeps): BridgeApiClient {
|
|||||||
)
|
)
|
||||||
|
|
||||||
handleErrorStatus(response.status, response.data, 'Poll')
|
handleErrorStatus(response.status, response.data, 'Poll')
|
||||||
rcLog(`poll response: status=${response.status} hasData=${!!response.data} url=${deps.baseUrl}`)
|
rcLog(
|
||||||
|
`poll response: status=${response.status} hasData=${!!response.data} url=${deps.baseUrl}`,
|
||||||
|
)
|
||||||
|
|
||||||
// Empty body or null = no work available
|
// Empty body or null = no work available
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
|
|||||||
@@ -448,9 +448,11 @@ export async function runBridgeLoop(
|
|||||||
): (status: SessionDoneStatus) => void {
|
): (status: SessionDoneStatus) => void {
|
||||||
return (rawStatus: SessionDoneStatus): void => {
|
return (rawStatus: SessionDoneStatus): void => {
|
||||||
const workId = sessionWorkIds.get(sessionId)
|
const workId = sessionWorkIds.get(sessionId)
|
||||||
rcLog(`session done: sessionId=${sessionId} workId=${workId ?? 'none'} status=${rawStatus}` +
|
rcLog(
|
||||||
` wasTimedOut=${timedOutSessions.has(sessionId)} duration=${Math.round((Date.now() - startTime) / 1000)}s` +
|
`session done: sessionId=${sessionId} workId=${workId ?? 'none'} status=${rawStatus}` +
|
||||||
` stderr=${handle.lastStderr.length > 0 ? handle.lastStderr.join('\\n').slice(0, 500) : '(none)'}`)
|
` wasTimedOut=${timedOutSessions.has(sessionId)} duration=${Math.round((Date.now() - startTime) / 1000)}s` +
|
||||||
|
` stderr=${handle.lastStderr.length > 0 ? handle.lastStderr.join('\\n').slice(0, 500) : '(none)'}`,
|
||||||
|
)
|
||||||
activeSessions.delete(sessionId)
|
activeSessions.delete(sessionId)
|
||||||
sessionStartTimes.delete(sessionId)
|
sessionStartTimes.delete(sessionId)
|
||||||
sessionWorkIds.delete(sessionId)
|
sessionWorkIds.delete(sessionId)
|
||||||
@@ -609,7 +611,9 @@ export async function runBridgeLoop(
|
|||||||
const pollConfig = getPollIntervalConfig()
|
const pollConfig = getPollIntervalConfig()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
rcLog(`poll: envId=${environmentId} activeSessions=${activeSessions.size}`)
|
rcLog(
|
||||||
|
`poll: envId=${environmentId} activeSessions=${activeSessions.size}`,
|
||||||
|
)
|
||||||
const work = await api.pollForWork(
|
const work = await api.pollForWork(
|
||||||
environmentId,
|
environmentId,
|
||||||
environmentSecret,
|
environmentSecret,
|
||||||
@@ -864,7 +868,9 @@ export async function runBridgeLoop(
|
|||||||
break
|
break
|
||||||
case 'session': {
|
case 'session': {
|
||||||
const sessionId = work.data.id
|
const sessionId = work.data.id
|
||||||
rcLog(`work received: type=session sessionId=${sessionId} workId=${work.id}`)
|
rcLog(
|
||||||
|
`work received: type=session sessionId=${sessionId} workId=${work.id}`,
|
||||||
|
)
|
||||||
try {
|
try {
|
||||||
validateBridgeId(sessionId, 'session_id')
|
validateBridgeId(sessionId, 'session_id')
|
||||||
} catch {
|
} catch {
|
||||||
@@ -1032,9 +1038,9 @@ export async function runBridgeLoop(
|
|||||||
|
|
||||||
rcLog(
|
rcLog(
|
||||||
`spawning session: sessionId=${sessionId} sdkUrl=${sdkUrl}` +
|
`spawning session: sessionId=${sessionId} sdkUrl=${sdkUrl}` +
|
||||||
` useCcrV2=${useCcrV2} workerEpoch=${workerEpoch}` +
|
` useCcrV2=${useCcrV2} workerEpoch=${workerEpoch}` +
|
||||||
` dir=${sessionDir}` +
|
` dir=${sessionDir}` +
|
||||||
` accessToken=${secret.session_ingress_token ? secret.session_ingress_token.slice(0, 8) + '...' : 'NONE'}`,
|
` accessToken=${secret.session_ingress_token ? secret.session_ingress_token.slice(0, 8) + '...' : 'NONE'}`,
|
||||||
)
|
)
|
||||||
const spawnResult = safeSpawn(
|
const spawnResult = safeSpawn(
|
||||||
spawner,
|
spawner,
|
||||||
@@ -1281,8 +1287,8 @@ export async function runBridgeLoop(
|
|||||||
const errMsg = describeAxiosError(err)
|
const errMsg = describeAxiosError(err)
|
||||||
rcLog(
|
rcLog(
|
||||||
`poll error: ${errMsg}` +
|
`poll error: ${errMsg}` +
|
||||||
` isConn=${isConnectionError(err)} isServer=${isServerError(err)}` +
|
` isConn=${isConnectionError(err)} isServer=${isServerError(err)}` +
|
||||||
` activeSessions=${activeSessions.size}`,
|
` activeSessions=${activeSessions.size}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isConnectionError(err) || isServerError(err)) {
|
if (isConnectionError(err) || isServerError(err)) {
|
||||||
@@ -1676,7 +1682,7 @@ async function stopWorkWithRetry(
|
|||||||
}
|
}
|
||||||
const errMsg = errorMessage(err)
|
const errMsg = errorMessage(err)
|
||||||
if (attempt < MAX_ATTEMPTS) {
|
if (attempt < MAX_ATTEMPTS) {
|
||||||
const delay = addJitter(baseDelayMs * Math.pow(2, attempt - 1))
|
const delay = addJitter(baseDelayMs * 2 ** (attempt - 1))
|
||||||
logger.logVerbose(
|
logger.logVerbose(
|
||||||
`Failed to stop work ${workId} (attempt ${attempt}/${MAX_ATTEMPTS}), retrying in ${formatDelay(delay)}: ${errMsg}`,
|
`Failed to stop work ${workId} (attempt ${attempt}/${MAX_ATTEMPTS}), retrying in ${formatDelay(delay)}: ${errMsg}`,
|
||||||
)
|
)
|
||||||
@@ -1964,7 +1970,6 @@ NOTES
|
|||||||
- You must be logged in with a Claude account that has a subscription
|
- You must be logged in with a Claude account that has a subscription
|
||||||
- Run \`claude\` first in the directory to accept the workspace trust dialog
|
- Run \`claude\` first in the directory to accept the workspace trust dialog
|
||||||
${serverNote}`
|
${serverNote}`
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional help output
|
|
||||||
console.log(help)
|
console.log(help)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2003,7 +2008,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (parsed.error) {
|
if (parsed.error) {
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional error output
|
|
||||||
console.error(`Error: ${parsed.error}`)
|
console.error(`Error: ${parsed.error}`)
|
||||||
// eslint-disable-next-line custom-rules/no-process-exit
|
// eslint-disable-next-line custom-rules/no-process-exit
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
@@ -2042,7 +2046,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
const { PERMISSION_MODES } = await import('../types/permissions.js')
|
const { PERMISSION_MODES } = await import('../types/permissions.js')
|
||||||
const valid: readonly string[] = PERMISSION_MODES
|
const valid: readonly string[] = PERMISSION_MODES
|
||||||
if (!valid.includes(permissionMode)) {
|
if (!valid.includes(permissionMode)) {
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional error output
|
|
||||||
console.error(
|
console.error(
|
||||||
`Error: Invalid permission mode '${permissionMode}'. Valid modes: ${valid.join(', ')}`,
|
`Error: Invalid permission mode '${permissionMode}'. Valid modes: ${valid.join(', ')}`,
|
||||||
)
|
)
|
||||||
@@ -2085,7 +2088,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
Promise.all([shutdown1PEventLogging(), shutdownDatadog()]),
|
Promise.all([shutdown1PEventLogging(), shutdownDatadog()]),
|
||||||
sleep(500, undefined, { unref: true }),
|
sleep(500, undefined, { unref: true }),
|
||||||
]).catch(() => {})
|
]).catch(() => {})
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional error output
|
|
||||||
console.error(
|
console.error(
|
||||||
'Error: Multi-session Remote Control is not enabled for your account yet.',
|
'Error: Multi-session Remote Control is not enabled for your account yet.',
|
||||||
)
|
)
|
||||||
@@ -2102,7 +2104,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
// The bridge bypasses main.tsx (which renders the interactive TrustDialog via showSetupScreens),
|
// The bridge bypasses main.tsx (which renders the interactive TrustDialog via showSetupScreens),
|
||||||
// so we must verify trust was previously established by a normal `claude` session.
|
// so we must verify trust was previously established by a normal `claude` session.
|
||||||
if (!checkHasTrustDialogAccepted()) {
|
if (!checkHasTrustDialogAccepted()) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.error(
|
console.error(
|
||||||
`Error: Workspace not trusted. Please run \`claude\` in ${dir} first to review and accept the workspace trust dialog.`,
|
`Error: Workspace not trusted. Please run \`claude\` in ${dir} first to review and accept the workspace trust dialog.`,
|
||||||
)
|
)
|
||||||
@@ -2119,7 +2120,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
|
|
||||||
const bridgeToken = getBridgeAccessToken()
|
const bridgeToken = getBridgeAccessToken()
|
||||||
if (!bridgeToken) {
|
if (!bridgeToken) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.error(BRIDGE_LOGIN_ERROR)
|
console.error(BRIDGE_LOGIN_ERROR)
|
||||||
// eslint-disable-next-line custom-rules/no-process-exit
|
// eslint-disable-next-line custom-rules/no-process-exit
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
@@ -2138,7 +2138,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout,
|
output: process.stdout,
|
||||||
})
|
})
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(
|
console.log(
|
||||||
'\nRemote Control lets you access this CLI session from the web (claude.ai/code)\nor the Claude app, so you can pick up where you left off on any device.\n\nYou can disconnect remote access anytime by running /remote-control again.\n',
|
'\nRemote Control lets you access this CLI session from the web (claude.ai/code)\nor the Claude app, so you can pick up where you left off on any device.\n\nYou can disconnect remote access anytime by running /remote-control again.\n',
|
||||||
)
|
)
|
||||||
@@ -2170,7 +2169,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
)
|
)
|
||||||
const found = await readBridgePointerAcrossWorktrees(dir)
|
const found = await readBridgePointerAcrossWorktrees(dir)
|
||||||
if (!found) {
|
if (!found) {
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional error output
|
|
||||||
console.error(
|
console.error(
|
||||||
`Error: No recent session found in this directory or its worktrees. Run \`claude remote-control\` to start a new one.`,
|
`Error: No recent session found in this directory or its worktrees. Run \`claude remote-control\` to start a new one.`,
|
||||||
)
|
)
|
||||||
@@ -2181,7 +2179,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
const ageMin = Math.round(pointer.ageMs / 60_000)
|
const ageMin = Math.round(pointer.ageMs / 60_000)
|
||||||
const ageStr = ageMin < 60 ? `${ageMin}m` : `${Math.round(ageMin / 60)}h`
|
const ageStr = ageMin < 60 ? `${ageMin}m` : `${Math.round(ageMin / 60)}h`
|
||||||
const fromWt = pointerDir !== dir ? ` from worktree ${pointerDir}` : ''
|
const fromWt = pointerDir !== dir ? ` from worktree ${pointerDir}` : ''
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional info output
|
|
||||||
console.error(
|
console.error(
|
||||||
`Resuming session ${pointer.sessionId} (${ageStr} ago)${fromWt}\u2026`,
|
`Resuming session ${pointer.sessionId} (${ageStr} ago)${fromWt}\u2026`,
|
||||||
)
|
)
|
||||||
@@ -2202,7 +2199,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
!baseUrl.includes('localhost') &&
|
!baseUrl.includes('localhost') &&
|
||||||
!baseUrl.includes('127.0.0.1')
|
!baseUrl.includes('127.0.0.1')
|
||||||
) {
|
) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.error(
|
console.error(
|
||||||
'Error: Remote Control base URL uses HTTP. Only HTTPS or localhost HTTP is allowed.',
|
'Error: Remote Control base URL uses HTTP. Only HTTPS or localhost HTTP is allowed.',
|
||||||
)
|
)
|
||||||
@@ -2238,7 +2234,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
? getCurrentProjectConfig().remoteControlSpawnMode
|
? getCurrentProjectConfig().remoteControlSpawnMode
|
||||||
: undefined
|
: undefined
|
||||||
if (savedSpawnMode === 'worktree' && !worktreeAvailable) {
|
if (savedSpawnMode === 'worktree' && !worktreeAvailable) {
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional warning output
|
|
||||||
console.error(
|
console.error(
|
||||||
'Warning: Saved spawn mode is worktree but this directory is not a git repository. Falling back to same-dir.',
|
'Warning: Saved spawn mode is worktree but this directory is not a git repository. Falling back to same-dir.',
|
||||||
)
|
)
|
||||||
@@ -2265,7 +2260,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout,
|
output: process.stdout,
|
||||||
})
|
})
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional dialog output
|
|
||||||
console.log(
|
console.log(
|
||||||
`\nClaude Remote Control is launching in spawn mode which lets you create new sessions in this project from Claude Code on Web or your Mobile app. Learn more here: https://code.claude.com/docs/en/remote-control\n\n` +
|
`\nClaude Remote Control is launching in spawn mode which lets you create new sessions in this project from Claude Code on Web or your Mobile app. Learn more here: https://code.claude.com/docs/en/remote-control\n\n` +
|
||||||
`Spawn mode for this project:\n` +
|
`Spawn mode for this project:\n` +
|
||||||
@@ -2344,7 +2338,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
// Only reachable via explicit --spawn=worktree (default is same-dir);
|
// Only reachable via explicit --spawn=worktree (default is same-dir);
|
||||||
// saved worktree pref was already guarded above.
|
// saved worktree pref was already guarded above.
|
||||||
if (spawnMode === 'worktree' && !worktreeAvailable) {
|
if (spawnMode === 'worktree' && !worktreeAvailable) {
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional error output
|
|
||||||
console.error(
|
console.error(
|
||||||
`Error: Worktree mode requires a git repository or WorktreeCreate hooks configured. Use --spawn=session for single-session mode.`,
|
`Error: Worktree mode requires a git repository or WorktreeCreate hooks configured. Use --spawn=session for single-session mode.`,
|
||||||
)
|
)
|
||||||
@@ -2379,7 +2372,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
validateBridgeId(resumeSessionId, 'sessionId')
|
validateBridgeId(resumeSessionId, 'sessionId')
|
||||||
} catch {
|
} catch {
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional error output
|
|
||||||
console.error(
|
console.error(
|
||||||
`Error: Invalid session ID "${resumeSessionId}". Session IDs must not contain unsafe characters.`,
|
`Error: Invalid session ID "${resumeSessionId}". Session IDs must not contain unsafe characters.`,
|
||||||
)
|
)
|
||||||
@@ -2405,7 +2397,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
const { clearBridgePointer } = await import('./bridgePointer.js')
|
const { clearBridgePointer } = await import('./bridgePointer.js')
|
||||||
await clearBridgePointer(resumePointerDir)
|
await clearBridgePointer(resumePointerDir)
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional error output
|
|
||||||
console.error(
|
console.error(
|
||||||
`Error: Session ${resumeSessionId} not found. It may have been archived or expired, or your login may have lapsed (run \`claude /login\`).`,
|
`Error: Session ${resumeSessionId} not found. It may have been archived or expired, or your login may have lapsed (run \`claude /login\`).`,
|
||||||
)
|
)
|
||||||
@@ -2417,7 +2408,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
const { clearBridgePointer } = await import('./bridgePointer.js')
|
const { clearBridgePointer } = await import('./bridgePointer.js')
|
||||||
await clearBridgePointer(resumePointerDir)
|
await clearBridgePointer(resumePointerDir)
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional error output
|
|
||||||
console.error(
|
console.error(
|
||||||
`Error: Session ${resumeSessionId} has no environment_id. It may never have been attached to a bridge.`,
|
`Error: Session ${resumeSessionId} has no environment_id. It may never have been attached to a bridge.`,
|
||||||
)
|
)
|
||||||
@@ -2471,7 +2461,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
status: err instanceof BridgeFatalError ? err.status : undefined,
|
status: err instanceof BridgeFatalError ? err.status : undefined,
|
||||||
})
|
})
|
||||||
// Registration failures are fatal — print a clean message instead of a stack trace.
|
// Registration failures are fatal — print a clean message instead of a stack trace.
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.error(
|
console.error(
|
||||||
err instanceof BridgeFatalError && err.status === 404
|
err instanceof BridgeFatalError && err.status === 404
|
||||||
? 'Remote Control environments are not available for your account.'
|
? 'Remote Control environments are not available for your account.'
|
||||||
@@ -2496,7 +2485,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
`Bridge resume env mismatch: requested ${reuseEnvironmentId}, backend returned ${environmentId}. Falling back to fresh session.`,
|
`Bridge resume env mismatch: requested ${reuseEnvironmentId}, backend returned ${environmentId}. Falling back to fresh session.`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional warning output
|
|
||||||
console.warn(
|
console.warn(
|
||||||
`Warning: Could not resume session ${resumeSessionId} — its environment has expired. Creating a fresh session instead.`,
|
`Warning: Could not resume session ${resumeSessionId} — its environment has expired. Creating a fresh session instead.`,
|
||||||
)
|
)
|
||||||
@@ -2547,7 +2535,6 @@ export async function bridgeMain(args: string[]): Promise<void> {
|
|||||||
const { clearBridgePointer } = await import('./bridgePointer.js')
|
const { clearBridgePointer } = await import('./bridgePointer.js')
|
||||||
await clearBridgePointer(resumePointerDir)
|
await clearBridgePointer(resumePointerDir)
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noConsole: intentional error output
|
|
||||||
console.error(
|
console.error(
|
||||||
isFatal
|
isFatal
|
||||||
? `Error: ${errorMessage(err)}`
|
? `Error: ${errorMessage(err)}`
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ export function isEligibleBridgeMessage(m: Message): boolean {
|
|||||||
export function extractTitleText(m: Message): string | undefined {
|
export function extractTitleText(m: Message): string | undefined {
|
||||||
if (m.type !== 'user' || m.isMeta || m.toolUseResult || m.isCompactSummary)
|
if (m.type !== 'user' || m.isMeta || m.toolUseResult || m.isCompactSummary)
|
||||||
return undefined
|
return undefined
|
||||||
if (m.origin && (m.origin as { kind?: string }).kind !== 'human') return undefined
|
if (m.origin && (m.origin as { kind?: string }).kind !== 'human')
|
||||||
|
return undefined
|
||||||
const content = m.message!.content
|
const content = m.message!.content
|
||||||
let raw: string | undefined
|
let raw: string | undefined
|
||||||
if (typeof content === 'string') {
|
if (typeof content === 'string') {
|
||||||
@@ -266,7 +267,13 @@ export function handleServerControlRequest(
|
|||||||
// Outbound-only: reply error for mutable requests so claude.ai doesn't show
|
// Outbound-only: reply error for mutable requests so claude.ai doesn't show
|
||||||
// false success. initialize must still succeed (server kills the connection
|
// false success. initialize must still succeed (server kills the connection
|
||||||
// if it doesn't — see comment above).
|
// if it doesn't — see comment above).
|
||||||
const req = request.request as { subtype: string; model?: string; max_thinking_tokens?: number | null; mode?: string; [key: string]: unknown }
|
const req = request.request as {
|
||||||
|
subtype: string
|
||||||
|
model?: string
|
||||||
|
max_thinking_tokens?: number | null
|
||||||
|
mode?: string
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
if (outboundOnly && req.subtype !== 'initialize') {
|
if (outboundOnly && req.subtype !== 'initialize') {
|
||||||
response = {
|
response = {
|
||||||
type: 'control_response',
|
type: 'control_response',
|
||||||
@@ -389,8 +396,8 @@ export function handleServerControlRequest(
|
|||||||
void transport.write(event)
|
void transport.write(event)
|
||||||
rcLog(
|
rcLog(
|
||||||
`control_response: subtype=${req.subtype}` +
|
`control_response: subtype=${req.subtype}` +
|
||||||
` request_id=${request.request_id}` +
|
` request_id=${request.request_id}` +
|
||||||
` result=${(response.response as { subtype?: string }).subtype}`,
|
` result=${(response.response as { subtype?: string }).subtype}`,
|
||||||
)
|
)
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Sent control_response for ${req.subtype} request_id=${request.request_id} result=${(response.response as { subtype?: string }).subtype}`,
|
`[bridge:repl] Sent control_response for ${req.subtype} request_id=${request.request_id} result=${(response.response as { subtype?: string }).subtype}`,
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ export function extractInboundMessageFields(
|
|||||||
| { content: string | Array<ContentBlockParam>; uuid: UUID | undefined }
|
| { content: string | Array<ContentBlockParam>; uuid: UUID | undefined }
|
||||||
| undefined {
|
| undefined {
|
||||||
if (msg.type !== 'user') return undefined
|
if (msg.type !== 'user') return undefined
|
||||||
const content = (msg.message as { content?: string | Array<ContentBlockParam> } | undefined)?.content
|
const content = (
|
||||||
|
msg.message as { content?: string | Array<ContentBlockParam> } | undefined
|
||||||
|
)?.content
|
||||||
if (!content) return undefined
|
if (!content) return undefined
|
||||||
if (Array.isArray(content) && content.length === 0) return undefined
|
if (Array.isArray(content) && content.length === 0) return undefined
|
||||||
|
|
||||||
|
|||||||
@@ -290,7 +290,9 @@ export async function initReplBridge(
|
|||||||
isSyntheticMessage(msg)
|
isSyntheticMessage(msg)
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
const rawContent = getContentText(msg.message!.content as string | ContentBlockParam[])
|
const rawContent = getContentText(
|
||||||
|
msg.message!.content as string | ContentBlockParam[],
|
||||||
|
)
|
||||||
if (!rawContent) continue
|
if (!rawContent) continue
|
||||||
const derived = deriveTitle(rawContent)
|
const derived = deriveTitle(rawContent)
|
||||||
if (!derived) continue
|
if (!derived) continue
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ export function rcLog(msg: string): void {
|
|||||||
try {
|
try {
|
||||||
if (!headerWritten) {
|
if (!headerWritten) {
|
||||||
ensureLogDir()
|
ensureLogDir()
|
||||||
appendFileSync(LOG_PATH, `\n===== RC-DEBUG session ${new Date().toISOString()} =====\n`)
|
appendFileSync(
|
||||||
|
LOG_PATH,
|
||||||
|
`\n===== RC-DEBUG session ${new Date().toISOString()} =====\n`,
|
||||||
|
)
|
||||||
headerWritten = true
|
headerWritten = true
|
||||||
}
|
}
|
||||||
const ts = new Date().toISOString().slice(11, 23) // HH:mm:ss.SSS
|
const ts = new Date().toISOString().slice(11, 23) // HH:mm:ss.SSS
|
||||||
|
|||||||
@@ -834,7 +834,10 @@ export async function initEnvLessBridgeCore(
|
|||||||
for (const msg of filtered) {
|
for (const msg of filtered) {
|
||||||
if (msg.uuid) recentPostedUUIDs.add(msg.uuid as string)
|
if (msg.uuid) recentPostedUUIDs.add(msg.uuid as string)
|
||||||
}
|
}
|
||||||
const events = filtered.map(m => ({ ...m, session_id: sessionId })) as StdoutMessage[]
|
const events = filtered.map(m => ({
|
||||||
|
...m,
|
||||||
|
session_id: sessionId,
|
||||||
|
})) as StdoutMessage[]
|
||||||
void transport.writeBatch(events)
|
void transport.writeBatch(events)
|
||||||
},
|
},
|
||||||
sendControlRequest(request: SDKControlRequest) {
|
sendControlRequest(request: SDKControlRequest) {
|
||||||
@@ -844,8 +847,14 @@ export async function initEnvLessBridgeCore(
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const event: TransportMessage = { ...request, session_id: sessionId } as TransportMessage
|
const event: TransportMessage = {
|
||||||
if ((request as { request?: { subtype?: string } }).request?.subtype === 'can_use_tool') {
|
...request,
|
||||||
|
session_id: sessionId,
|
||||||
|
} as TransportMessage
|
||||||
|
if (
|
||||||
|
(request as { request?: { subtype?: string } }).request?.subtype ===
|
||||||
|
'can_use_tool'
|
||||||
|
) {
|
||||||
transport.reportState('requires_action')
|
transport.reportState('requires_action')
|
||||||
}
|
}
|
||||||
void transport.write(event as StdoutMessage)
|
void transport.write(event as StdoutMessage)
|
||||||
@@ -860,7 +869,10 @@ export async function initEnvLessBridgeCore(
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const event: TransportMessage = { ...response, session_id: sessionId } as TransportMessage
|
const event: TransportMessage = {
|
||||||
|
...response,
|
||||||
|
session_id: sessionId,
|
||||||
|
} as TransportMessage
|
||||||
transport.reportState('running')
|
transport.reportState('running')
|
||||||
void transport.write(event as StdoutMessage)
|
void transport.write(event as StdoutMessage)
|
||||||
logForDebugging('[remote-bridge] Sent control_response')
|
logForDebugging('[remote-bridge] Sent control_response')
|
||||||
|
|||||||
@@ -452,7 +452,6 @@ export async function initBridgeCore(
|
|||||||
// re-created after a connection loss.
|
// re-created after a connection loss.
|
||||||
let currentSessionId: string
|
let currentSessionId: string
|
||||||
|
|
||||||
|
|
||||||
if (reusedPriorSession && prior) {
|
if (reusedPriorSession && prior) {
|
||||||
currentSessionId = prior.sessionId
|
currentSessionId = prior.sessionId
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
@@ -632,9 +631,9 @@ export async function initBridgeCore(
|
|||||||
environmentRecreations++
|
environmentRecreations++
|
||||||
rcLog(
|
rcLog(
|
||||||
`doReconnect: attempt=${environmentRecreations}/${MAX_ENVIRONMENT_RECREATIONS}` +
|
`doReconnect: attempt=${environmentRecreations}/${MAX_ENVIRONMENT_RECREATIONS}` +
|
||||||
` envId=${environmentId}` +
|
` envId=${environmentId}` +
|
||||||
` sessionId=${currentSessionId}` +
|
` sessionId=${currentSessionId}` +
|
||||||
` workId=${currentWorkId}`,
|
` workId=${currentWorkId}`,
|
||||||
)
|
)
|
||||||
// Invalidate any in-flight v2 handshake — the environment is being
|
// Invalidate any in-flight v2 handshake — the environment is being
|
||||||
// recreated, so a stale transport arriving post-reconnect would be
|
// recreated, so a stale transport arriving post-reconnect would be
|
||||||
@@ -846,7 +845,6 @@ export async function initBridgeCore(
|
|||||||
// UUIDs are scoped per-session on the server, so re-flushing is safe.
|
// UUIDs are scoped per-session on the server, so re-flushing is safe.
|
||||||
previouslyFlushedUUIDs?.clear()
|
previouslyFlushedUUIDs?.clear()
|
||||||
|
|
||||||
|
|
||||||
// Reset the counter so independent reconnections hours apart don't
|
// Reset the counter so independent reconnections hours apart don't
|
||||||
// exhaust the limit — it guards against rapid consecutive failures,
|
// exhaust the limit — it guards against rapid consecutive failures,
|
||||||
// not lifetime total.
|
// not lifetime total.
|
||||||
@@ -907,8 +905,8 @@ export async function initBridgeCore(
|
|||||||
function handleTransportPermanentClose(closeCode: number | undefined): void {
|
function handleTransportPermanentClose(closeCode: number | undefined): void {
|
||||||
rcLog(
|
rcLog(
|
||||||
`handleTransportPermanentClose: code=${closeCode}` +
|
`handleTransportPermanentClose: code=${closeCode}` +
|
||||||
` transport=${transport ? 'exists' : 'null'}` +
|
` transport=${transport ? 'exists' : 'null'}` +
|
||||||
` pollAborted=${pollController.signal.aborted}`,
|
` pollAborted=${pollController.signal.aborted}`,
|
||||||
)
|
)
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Transport permanently closed: code=${closeCode}`,
|
`[bridge:repl] Transport permanently closed: code=${closeCode}`,
|
||||||
@@ -1303,7 +1301,9 @@ export async function initBridgeCore(
|
|||||||
session_id: currentSessionId,
|
session_id: currentSessionId,
|
||||||
})) as TransportMessage[]
|
})) as TransportMessage[]
|
||||||
const dropsBefore = newTransport.droppedBatchCount
|
const dropsBefore = newTransport.droppedBatchCount
|
||||||
void newTransport.writeBatch(events as StdoutMessage[]).then(() => {
|
void newTransport
|
||||||
|
.writeBatch(events as StdoutMessage[])
|
||||||
|
.then(() => {
|
||||||
// If any batch was dropped during this flush (SI down for
|
// If any batch was dropped during this flush (SI down for
|
||||||
// maxConsecutiveFailures attempts), flush() still resolved
|
// maxConsecutiveFailures attempts), flush() still resolved
|
||||||
// normally but the events were NOT delivered. Don't mark
|
// normally but the events were NOT delivered. Don't mark
|
||||||
@@ -1357,10 +1357,10 @@ export async function initBridgeCore(
|
|||||||
const parsed = JSON.parse(data)
|
const parsed = JSON.parse(data)
|
||||||
rcLog(
|
rcLog(
|
||||||
`ingress: type=${parsed.type}` +
|
`ingress: type=${parsed.type}` +
|
||||||
`${parsed.type === 'control_request' ? ` subtype=${(parsed.request as Record<string, unknown>)?.subtype} request_id=${parsed.request_id}` : ''}` +
|
`${parsed.type === 'control_request' ? ` subtype=${(parsed.request as Record<string, unknown>)?.subtype} request_id=${parsed.request_id}` : ''}` +
|
||||||
`${parsed.type === 'control_response' ? ` subtype=${(parsed.response as Record<string, unknown>)?.subtype} request_id=${(parsed.response as Record<string, unknown>)?.request_id}` : ''}` +
|
`${parsed.type === 'control_response' ? ` subtype=${(parsed.response as Record<string, unknown>)?.subtype} request_id=${(parsed.response as Record<string, unknown>)?.request_id}` : ''}` +
|
||||||
`${parsed.type === 'user' ? ` uuid=${parsed.uuid}` : ''}` +
|
`${parsed.type === 'user' ? ` uuid=${parsed.uuid}` : ''}` +
|
||||||
`${parsed.type === 'keep_alive' ? '' : ` len=${data.length}`}`,
|
`${parsed.type === 'keep_alive' ? '' : ` len=${data.length}`}`,
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
rcLog(`ingress (non-JSON): ${String(data).slice(0, 200)}`)
|
rcLog(`ingress (non-JSON): ${String(data).slice(0, 200)}`)
|
||||||
@@ -1387,9 +1387,9 @@ export async function initBridgeCore(
|
|||||||
if (transport !== newTransport) return
|
if (transport !== newTransport) return
|
||||||
rcLog(
|
rcLog(
|
||||||
`transport onClose: code=${closeCode}` +
|
`transport onClose: code=${closeCode}` +
|
||||||
` connected=${newTransport.isConnectedStatus()}` +
|
` connected=${newTransport.isConnectedStatus()}` +
|
||||||
` state=${newTransport.getStateLabel()}` +
|
` state=${newTransport.getStateLabel()}` +
|
||||||
` seq=${newTransport.getLastSequenceNum()}`,
|
` seq=${newTransport.getLastSequenceNum()}`,
|
||||||
)
|
)
|
||||||
handleTransportPermanentClose(closeCode)
|
handleTransportPermanentClose(closeCode)
|
||||||
})
|
})
|
||||||
@@ -1818,7 +1818,10 @@ export async function initBridgeCore(
|
|||||||
for (const msg of filtered) {
|
for (const msg of filtered) {
|
||||||
if (msg.uuid) recentPostedUUIDs.add(msg.uuid as string)
|
if (msg.uuid) recentPostedUUIDs.add(msg.uuid as string)
|
||||||
}
|
}
|
||||||
const events: TransportMessage[] = filtered.map(m => ({ ...m, session_id: currentSessionId })) as TransportMessage[]
|
const events: TransportMessage[] = filtered.map(m => ({
|
||||||
|
...m,
|
||||||
|
session_id: currentSessionId,
|
||||||
|
})) as TransportMessage[]
|
||||||
void transport.writeBatch(events as StdoutMessage[])
|
void transport.writeBatch(events as StdoutMessage[])
|
||||||
},
|
},
|
||||||
sendControlRequest(request: SDKControlRequest) {
|
sendControlRequest(request: SDKControlRequest) {
|
||||||
@@ -1828,7 +1831,10 @@ export async function initBridgeCore(
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const event: TransportMessage = { ...request, session_id: currentSessionId } as TransportMessage
|
const event: TransportMessage = {
|
||||||
|
...request,
|
||||||
|
session_id: currentSessionId,
|
||||||
|
} as TransportMessage
|
||||||
void transport.write(event as StdoutMessage)
|
void transport.write(event as StdoutMessage)
|
||||||
logForDebugging(
|
logForDebugging(
|
||||||
`[bridge:repl] Sent control_request request_id=${request.request_id}`,
|
`[bridge:repl] Sent control_request request_id=${request.request_id}`,
|
||||||
@@ -1841,7 +1847,10 @@ export async function initBridgeCore(
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const event: TransportMessage = { ...response, session_id: currentSessionId } as TransportMessage
|
const event: TransportMessage = {
|
||||||
|
...response,
|
||||||
|
session_id: currentSessionId,
|
||||||
|
} as TransportMessage
|
||||||
void transport.write(event as StdoutMessage)
|
void transport.write(event as StdoutMessage)
|
||||||
logForDebugging('[bridge:repl] Sent control_response')
|
logForDebugging('[bridge:repl] Sent control_response')
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type StdoutMessage = any;
|
export type StdoutMessage = any
|
||||||
|
|||||||
@@ -11,21 +11,44 @@
|
|||||||
/** Patterns that match known secret/token formats. */
|
/** Patterns that match known secret/token formats. */
|
||||||
const SECRET_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [
|
const SECRET_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [
|
||||||
// GitHub tokens (PAT, OAuth, App, Server-to-server)
|
// GitHub tokens (PAT, OAuth, App, Server-to-server)
|
||||||
{ pattern: /\b(ghp|gho|ghs|ghu|github_pat)_[A-Za-z0-9_]{10,}\b/g, replacement: '[REDACTED_GITHUB_TOKEN]' },
|
{
|
||||||
|
pattern: /\b(ghp|gho|ghs|ghu|github_pat)_[A-Za-z0-9_]{10,}\b/g,
|
||||||
|
replacement: '[REDACTED_GITHUB_TOKEN]',
|
||||||
|
},
|
||||||
// Anthropic API keys
|
// Anthropic API keys
|
||||||
{ pattern: /\bsk-ant-[A-Za-z0-9_-]{10,}\b/g, replacement: '[REDACTED_ANTHROPIC_KEY]' },
|
{
|
||||||
|
pattern: /\bsk-ant-[A-Za-z0-9_-]{10,}\b/g,
|
||||||
|
replacement: '[REDACTED_ANTHROPIC_KEY]',
|
||||||
|
},
|
||||||
// Generic Bearer tokens in headers
|
// Generic Bearer tokens in headers
|
||||||
{ pattern: /(Bearer\s+)[A-Za-z0-9._\-/+=]{20,}/gi, replacement: '$1[REDACTED_TOKEN]' },
|
{
|
||||||
|
pattern: /(Bearer\s+)[A-Za-z0-9._\-/+=]{20,}/gi,
|
||||||
|
replacement: '$1[REDACTED_TOKEN]',
|
||||||
|
},
|
||||||
// AWS access keys
|
// AWS access keys
|
||||||
{ pattern: /\b(AKIA|ASIA)[A-Z0-9]{16}\b/g, replacement: '[REDACTED_AWS_KEY]' },
|
{
|
||||||
|
pattern: /\b(AKIA|ASIA)[A-Z0-9]{16}\b/g,
|
||||||
|
replacement: '[REDACTED_AWS_KEY]',
|
||||||
|
},
|
||||||
// AWS secret keys (40-char base64-like strings after common labels)
|
// AWS secret keys (40-char base64-like strings after common labels)
|
||||||
{ pattern: /(aws_secret_access_key|secret_key|SecretAccessKey)['":\s=]+[A-Za-z0-9/+=]{30,}/gi, replacement: '$1=[REDACTED_AWS_SECRET]' },
|
{
|
||||||
|
pattern:
|
||||||
|
/(aws_secret_access_key|secret_key|SecretAccessKey)['":\s=]+[A-Za-z0-9/+=]{30,}/gi,
|
||||||
|
replacement: '$1=[REDACTED_AWS_SECRET]',
|
||||||
|
},
|
||||||
// Generic API key patterns (key=value or "key": "value")
|
// Generic API key patterns (key=value or "key": "value")
|
||||||
{ pattern: /(api[_-]?key|apikey|secret|password|token|credential)['":\s=]+["']?[A-Za-z0-9._\-/+=]{16,}["']?/gi, replacement: '$1=[REDACTED]' },
|
{
|
||||||
|
pattern:
|
||||||
|
/(api[_-]?key|apikey|secret|password|token|credential)['":\s=]+["']?[A-Za-z0-9._\-/+=]{16,}["']?/gi,
|
||||||
|
replacement: '$1=[REDACTED]',
|
||||||
|
},
|
||||||
// npm tokens
|
// npm tokens
|
||||||
{ pattern: /\bnpm_[A-Za-z0-9]{36}\b/g, replacement: '[REDACTED_NPM_TOKEN]' },
|
{ pattern: /\bnpm_[A-Za-z0-9]{36}\b/g, replacement: '[REDACTED_NPM_TOKEN]' },
|
||||||
// Slack tokens
|
// Slack tokens
|
||||||
{ pattern: /\bxox[bporas]-[A-Za-z0-9-]{10,}\b/g, replacement: '[REDACTED_SLACK_TOKEN]' },
|
{
|
||||||
|
pattern: /\bxox[bporas]-[A-Za-z0-9-]{10,}\b/g,
|
||||||
|
replacement: '[REDACTED_SLACK_TOKEN]',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
/** Maximum content length before truncation (100KB). */
|
/** Maximum content length before truncation (100KB). */
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
import { feature } from 'bun:bundle'
|
import { feature } from 'bun:bundle';
|
||||||
import figures from 'figures'
|
import figures from 'figures';
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { useTerminalSize } from '../hooks/useTerminalSize.js'
|
import { useTerminalSize } from '../hooks/useTerminalSize.js';
|
||||||
import { Box, Text, stringWidth } from '@anthropic/ink'
|
import { Box, Text, stringWidth } from '@anthropic/ink';
|
||||||
import { useAppState, useSetAppState } from '../state/AppState.js'
|
import { useAppState, useSetAppState } from '../state/AppState.js';
|
||||||
import type { AppState } from '../state/AppStateStore.js'
|
import type { AppState } from '../state/AppStateStore.js';
|
||||||
import { getGlobalConfig } from '../utils/config.js'
|
import { getGlobalConfig } from '../utils/config.js';
|
||||||
import { isFullscreenActive } from '../utils/fullscreen.js'
|
import { isFullscreenActive } from '../utils/fullscreen.js';
|
||||||
import type { Theme } from '../utils/theme.js'
|
import type { Theme } from '../utils/theme.js';
|
||||||
import { getCompanion } from './companion.js'
|
import { getCompanion } from './companion.js';
|
||||||
import { renderFace, renderSprite, spriteFrameCount } from './sprites.js'
|
import { renderFace, renderSprite, spriteFrameCount } from './sprites.js';
|
||||||
import { RARITY_COLORS } from './types.js'
|
import { RARITY_COLORS } from './types.js';
|
||||||
|
|
||||||
const TICK_MS = 500
|
const TICK_MS = 500;
|
||||||
const BUBBLE_SHOW = 20 // ticks → ~10s at 500ms
|
const BUBBLE_SHOW = 20; // ticks → ~10s at 500ms
|
||||||
const FADE_WINDOW = 6 // last ~3s the bubble dims so you know it's about to go
|
const FADE_WINDOW = 6; // last ~3s the bubble dims so you know it's about to go
|
||||||
const PET_BURST_MS = 2500 // how long hearts float after /buddy pet
|
const PET_BURST_MS = 2500; // how long hearts float after /buddy pet
|
||||||
|
|
||||||
// Idle sequence: mostly rest (frame 0), occasional fidget (frames 1-2), rare blink.
|
// Idle sequence: mostly rest (frame 0), occasional fidget (frames 1-2), rare blink.
|
||||||
// Sequence indices map to sprite frames; -1 means "blink on frame 0".
|
// Sequence indices map to sprite frames; -1 means "blink on frame 0".
|
||||||
const IDLE_SEQUENCE = [0, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 2, 0, 0, 0]
|
const IDLE_SEQUENCE = [0, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0, 2, 0, 0, 0];
|
||||||
|
|
||||||
// Hearts float up-and-out over 5 ticks (~2.5s). Prepended above the sprite.
|
// Hearts float up-and-out over 5 ticks (~2.5s). Prepended above the sprite.
|
||||||
const H = figures.heart
|
const H = figures.heart;
|
||||||
const PET_HEARTS = [
|
const PET_HEARTS = [
|
||||||
` ${H} ${H} `,
|
` ${H} ${H} `,
|
||||||
` ${H} ${H} ${H} `,
|
` ${H} ${H} ${H} `,
|
||||||
` ${H} ${H} ${H} `,
|
` ${H} ${H} ${H} `,
|
||||||
`${H} ${H} ${H} `,
|
`${H} ${H} ${H} `,
|
||||||
'· · · ',
|
'· · · ',
|
||||||
]
|
];
|
||||||
|
|
||||||
function wrap(text: string, width: number): string[] {
|
function wrap(text: string, width: number): string[] {
|
||||||
const words = text.split(' ')
|
const words = text.split(' ');
|
||||||
const lines: string[] = []
|
const lines: string[] = [];
|
||||||
let cur = ''
|
let cur = '';
|
||||||
for (const w of words) {
|
for (const w of words) {
|
||||||
if (cur.length + w.length + 1 > width && cur) {
|
if (cur.length + w.length + 1 > width && cur) {
|
||||||
lines.push(cur)
|
lines.push(cur);
|
||||||
cur = w
|
cur = w;
|
||||||
} else {
|
} else {
|
||||||
cur = cur ? `${cur} ${w}` : w
|
cur = cur ? `${cur} ${w}` : w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cur) lines.push(cur)
|
if (cur) lines.push(cur);
|
||||||
return lines
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
function SpeechBubble({
|
function SpeechBubble({
|
||||||
@@ -53,40 +53,29 @@ function SpeechBubble({
|
|||||||
fading,
|
fading,
|
||||||
tail,
|
tail,
|
||||||
}: {
|
}: {
|
||||||
text: string
|
text: string;
|
||||||
color: keyof Theme
|
color: keyof Theme;
|
||||||
fading: boolean
|
fading: boolean;
|
||||||
tail: 'down' | 'right'
|
tail: 'down' | 'right';
|
||||||
}): React.ReactNode {
|
}): React.ReactNode {
|
||||||
const lines = wrap(text, 30)
|
const lines = wrap(text, 30);
|
||||||
const borderColor = fading ? 'inactive' : color
|
const borderColor = fading ? 'inactive' : color;
|
||||||
const bubble = (
|
const bubble = (
|
||||||
<Box
|
<Box flexDirection="column" borderStyle="round" borderColor={borderColor} paddingX={1} width={34}>
|
||||||
flexDirection="column"
|
|
||||||
borderStyle="round"
|
|
||||||
borderColor={borderColor}
|
|
||||||
paddingX={1}
|
|
||||||
width={34}
|
|
||||||
>
|
|
||||||
{lines.map((l, i) => (
|
{lines.map((l, i) => (
|
||||||
<Text
|
<Text key={i} italic dimColor={!fading} color={fading ? 'inactive' : undefined}>
|
||||||
key={i}
|
|
||||||
italic
|
|
||||||
dimColor={!fading}
|
|
||||||
color={fading ? 'inactive' : undefined}
|
|
||||||
>
|
|
||||||
{l}
|
{l}
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
if (tail === 'right') {
|
if (tail === 'right') {
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="row" alignItems="center">
|
<Box flexDirection="row" alignItems="center">
|
||||||
{bubble}
|
{bubble}
|
||||||
<Text color={borderColor}>─</Text>
|
<Text color={borderColor}>─</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" alignItems="flex-end" marginRight={1}>
|
<Box flexDirection="column" alignItems="flex-end" marginRight={1}>
|
||||||
@@ -96,18 +85,18 @@ function SpeechBubble({
|
|||||||
<Text color={borderColor}>╲</Text>
|
<Text color={borderColor}>╲</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MIN_COLS_FOR_FULL_SPRITE = 100
|
export const MIN_COLS_FOR_FULL_SPRITE = 100;
|
||||||
const SPRITE_BODY_WIDTH = 12
|
const SPRITE_BODY_WIDTH = 12;
|
||||||
const NAME_ROW_PAD = 2 // focused state wraps name in spaces: ` name `
|
const NAME_ROW_PAD = 2; // focused state wraps name in spaces: ` name `
|
||||||
const SPRITE_PADDING_X = 2
|
const SPRITE_PADDING_X = 2;
|
||||||
const BUBBLE_WIDTH = 36 // SpeechBubble box (34) + tail column
|
const BUBBLE_WIDTH = 36; // SpeechBubble box (34) + tail column
|
||||||
const NARROW_QUIP_CAP = 24
|
const NARROW_QUIP_CAP = 24;
|
||||||
|
|
||||||
function spriteColWidth(nameWidth: number): number {
|
function spriteColWidth(nameWidth: number): number {
|
||||||
return Math.max(SPRITE_BODY_WIDTH, nameWidth + NAME_ROW_PAD)
|
return Math.max(SPRITE_BODY_WIDTH, nameWidth + NAME_ROW_PAD);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Width the sprite area consumes. PromptInput subtracts this so text wraps
|
// Width the sprite area consumes. PromptInput subtracts this so text wraps
|
||||||
@@ -115,89 +104,73 @@ function spriteColWidth(nameWidth: number): number {
|
|||||||
// width); in non-fullscreen it sits inline and needs BUBBLE_WIDTH more.
|
// width); in non-fullscreen it sits inline and needs BUBBLE_WIDTH more.
|
||||||
// Narrow terminals: 0 — REPL.tsx stacks the one-liner on its own row
|
// Narrow terminals: 0 — REPL.tsx stacks the one-liner on its own row
|
||||||
// (above input in fullscreen, below in scrollback), so no reservation.
|
// (above input in fullscreen, below in scrollback), so no reservation.
|
||||||
export function companionReservedColumns(
|
export function companionReservedColumns(terminalColumns: number, speaking: boolean): number {
|
||||||
terminalColumns: number,
|
if (!feature('BUDDY')) return 0;
|
||||||
speaking: boolean,
|
const companion = getCompanion();
|
||||||
): number {
|
if (!companion || getGlobalConfig().companionMuted) return 0;
|
||||||
if (!feature('BUDDY')) return 0
|
if (terminalColumns < MIN_COLS_FOR_FULL_SPRITE) return 0;
|
||||||
const companion = getCompanion()
|
const nameWidth = stringWidth(companion.name);
|
||||||
if (!companion || getGlobalConfig().companionMuted) return 0
|
const bubble = speaking && !isFullscreenActive() ? BUBBLE_WIDTH : 0;
|
||||||
if (terminalColumns < MIN_COLS_FOR_FULL_SPRITE) return 0
|
return spriteColWidth(nameWidth) + SPRITE_PADDING_X + bubble;
|
||||||
const nameWidth = stringWidth(companion.name)
|
|
||||||
const bubble = speaking && !isFullscreenActive() ? BUBBLE_WIDTH : 0
|
|
||||||
return spriteColWidth(nameWidth) + SPRITE_PADDING_X + bubble
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CompanionSprite(): React.ReactNode {
|
export function CompanionSprite(): React.ReactNode {
|
||||||
const reaction = useAppState(s => s.companionReaction)
|
const reaction = useAppState(s => s.companionReaction);
|
||||||
const petAt = useAppState(s => s.companionPetAt)
|
const petAt = useAppState(s => s.companionPetAt);
|
||||||
const focused = useAppState(s => s.footerSelection === 'companion')
|
const focused = useAppState(s => s.footerSelection === 'companion');
|
||||||
const setAppState = useSetAppState()
|
const setAppState = useSetAppState();
|
||||||
const { columns } = useTerminalSize()
|
const { columns } = useTerminalSize();
|
||||||
const [tick, setTick] = useState(0)
|
const [tick, setTick] = useState(0);
|
||||||
const lastSpokeTick = useRef(0)
|
const lastSpokeTick = useRef(0);
|
||||||
// Sync-during-render (not useEffect) so the first post-pet render already
|
// Sync-during-render (not useEffect) so the first post-pet render already
|
||||||
// has petStartTick=tick and petAge=0 — otherwise frame 0 is skipped.
|
// has petStartTick=tick and petAge=0 — otherwise frame 0 is skipped.
|
||||||
const [{ petStartTick, forPetAt }, setPetStart] = useState({
|
const [{ petStartTick, forPetAt }, setPetStart] = useState({
|
||||||
petStartTick: 0,
|
petStartTick: 0,
|
||||||
forPetAt: petAt,
|
forPetAt: petAt,
|
||||||
})
|
});
|
||||||
if (petAt !== forPetAt) {
|
if (petAt !== forPetAt) {
|
||||||
setPetStart({ petStartTick: tick, forPetAt: petAt })
|
setPetStart({ petStartTick: tick, forPetAt: petAt });
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setInterval(
|
const timer = setInterval(setT => setT((t: number) => t + 1), TICK_MS, setTick);
|
||||||
setT => setT((t: number) => t + 1),
|
return () => clearInterval(timer);
|
||||||
TICK_MS,
|
}, []);
|
||||||
setTick,
|
|
||||||
)
|
|
||||||
return () => clearInterval(timer)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!reaction) return
|
if (!reaction) return;
|
||||||
lastSpokeTick.current = tick
|
lastSpokeTick.current = tick;
|
||||||
const timer = setTimeout(
|
const timer = setTimeout(
|
||||||
setA =>
|
setA =>
|
||||||
setA((prev: AppState) =>
|
setA((prev: AppState) =>
|
||||||
prev.companionReaction === undefined
|
prev.companionReaction === undefined ? prev : { ...prev, companionReaction: undefined },
|
||||||
? prev
|
|
||||||
: { ...prev, companionReaction: undefined },
|
|
||||||
),
|
),
|
||||||
BUBBLE_SHOW * TICK_MS,
|
BUBBLE_SHOW * TICK_MS,
|
||||||
setAppState,
|
setAppState,
|
||||||
)
|
);
|
||||||
return () => clearTimeout(timer)
|
return () => clearTimeout(timer);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- tick intentionally captured at reaction-change, not tracked
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- tick intentionally captured at reaction-change, not tracked
|
||||||
}, [reaction, setAppState])
|
}, [reaction, setAppState]);
|
||||||
|
|
||||||
if (!feature('BUDDY')) return null
|
if (!feature('BUDDY')) return null;
|
||||||
const companion = getCompanion()
|
const companion = getCompanion();
|
||||||
if (!companion || getGlobalConfig().companionMuted) return null
|
if (!companion || getGlobalConfig().companionMuted) return null;
|
||||||
|
|
||||||
const color = RARITY_COLORS[companion.rarity]
|
const color = RARITY_COLORS[companion.rarity];
|
||||||
const colWidth = spriteColWidth(stringWidth(companion.name))
|
const colWidth = spriteColWidth(stringWidth(companion.name));
|
||||||
|
|
||||||
const bubbleAge = reaction ? tick - lastSpokeTick.current : 0
|
const bubbleAge = reaction ? tick - lastSpokeTick.current : 0;
|
||||||
const fading =
|
const fading = reaction !== undefined && bubbleAge >= BUBBLE_SHOW - FADE_WINDOW;
|
||||||
reaction !== undefined && bubbleAge >= BUBBLE_SHOW - FADE_WINDOW
|
|
||||||
|
|
||||||
const petAge = petAt ? tick - petStartTick : Infinity
|
const petAge = petAt ? tick - petStartTick : Infinity;
|
||||||
const petting = petAge * TICK_MS < PET_BURST_MS
|
const petting = petAge * TICK_MS < PET_BURST_MS;
|
||||||
|
|
||||||
// Narrow terminals: collapse to one-line face. When speaking, the quip
|
// Narrow terminals: collapse to one-line face. When speaking, the quip
|
||||||
// replaces the name beside the face (no room for a bubble).
|
// replaces the name beside the face (no room for a bubble).
|
||||||
if (columns < MIN_COLS_FOR_FULL_SPRITE) {
|
if (columns < MIN_COLS_FOR_FULL_SPRITE) {
|
||||||
const quip =
|
const quip =
|
||||||
reaction && reaction.length > NARROW_QUIP_CAP
|
reaction && reaction.length > NARROW_QUIP_CAP ? reaction.slice(0, NARROW_QUIP_CAP - 1) + '…' : reaction;
|
||||||
? reaction.slice(0, NARROW_QUIP_CAP - 1) + '…'
|
const label = quip ? `"${quip}"` : focused ? ` ${companion.name} ` : companion.name;
|
||||||
: reaction
|
|
||||||
const label = quip
|
|
||||||
? `"${quip}"`
|
|
||||||
: focused
|
|
||||||
? ` ${companion.name} `
|
|
||||||
: companion.name
|
|
||||||
return (
|
return (
|
||||||
<Box paddingX={1} alignSelf="flex-end">
|
<Box paddingX={1} alignSelf="flex-end">
|
||||||
<Text>
|
<Text>
|
||||||
@@ -210,44 +183,34 @@ export function CompanionSprite(): React.ReactNode {
|
|||||||
dimColor={!focused && !reaction}
|
dimColor={!focused && !reaction}
|
||||||
bold={focused}
|
bold={focused}
|
||||||
inverse={focused && !reaction}
|
inverse={focused && !reaction}
|
||||||
color={
|
color={reaction ? (fading ? 'inactive' : color) : focused ? color : undefined}
|
||||||
reaction
|
|
||||||
? fading
|
|
||||||
? 'inactive'
|
|
||||||
: color
|
|
||||||
: focused
|
|
||||||
? color
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
const frameCount = spriteFrameCount(companion.species)
|
const frameCount = spriteFrameCount(companion.species);
|
||||||
const heartFrame = petting ? PET_HEARTS[petAge % PET_HEARTS.length] : null
|
const heartFrame = petting ? PET_HEARTS[petAge % PET_HEARTS.length] : null;
|
||||||
|
|
||||||
let spriteFrame: number
|
let spriteFrame: number;
|
||||||
let blink = false
|
let blink = false;
|
||||||
if (reaction || petting) {
|
if (reaction || petting) {
|
||||||
// Excited: cycle all fidget frames fast
|
// Excited: cycle all fidget frames fast
|
||||||
spriteFrame = tick % frameCount
|
spriteFrame = tick % frameCount;
|
||||||
} else {
|
} else {
|
||||||
const step = IDLE_SEQUENCE[tick % IDLE_SEQUENCE.length]!
|
const step = IDLE_SEQUENCE[tick % IDLE_SEQUENCE.length]!;
|
||||||
if (step === -1) {
|
if (step === -1) {
|
||||||
spriteFrame = 0
|
spriteFrame = 0;
|
||||||
blink = true
|
blink = true;
|
||||||
} else {
|
} else {
|
||||||
spriteFrame = step % frameCount
|
spriteFrame = step % frameCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = renderSprite(companion, spriteFrame).map(line =>
|
const body = renderSprite(companion, spriteFrame).map(line => (blink ? line.replaceAll(companion.eye, '-') : line));
|
||||||
blink ? line.replaceAll(companion.eye, '-') : line,
|
const sprite = heartFrame ? [heartFrame, ...body] : body;
|
||||||
)
|
|
||||||
const sprite = heartFrame ? [heartFrame, ...body] : body
|
|
||||||
|
|
||||||
// Name row doubles as hint row — unfocused shows dim name + ↓ discovery,
|
// Name row doubles as hint row — unfocused shows dim name + ↓ discovery,
|
||||||
// focused shows inverse name. The enter-to-open hint lives in
|
// focused shows inverse name. The enter-to-open hint lives in
|
||||||
@@ -255,31 +218,20 @@ export function CompanionSprite(): React.ReactNode {
|
|||||||
// sprite doesn't jump up when selected. flexShrink=0 stops the
|
// sprite doesn't jump up when selected. flexShrink=0 stops the
|
||||||
// inline-bubble row wrapper from squeezing the sprite to fit.
|
// inline-bubble row wrapper from squeezing the sprite to fit.
|
||||||
const spriteColumn = (
|
const spriteColumn = (
|
||||||
<Box
|
<Box flexDirection="column" flexShrink={0} alignItems="center" width={colWidth}>
|
||||||
flexDirection="column"
|
|
||||||
flexShrink={0}
|
|
||||||
alignItems="center"
|
|
||||||
width={colWidth}
|
|
||||||
>
|
|
||||||
{sprite.map((line, i) => (
|
{sprite.map((line, i) => (
|
||||||
<Text key={i} color={i === 0 && heartFrame ? 'autoAccept' : color}>
|
<Text key={i} color={i === 0 && heartFrame ? 'autoAccept' : color}>
|
||||||
{line}
|
{line}
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
<Text
|
<Text italic bold={focused} dimColor={!focused} color={focused ? color : undefined} inverse={focused}>
|
||||||
italic
|
|
||||||
bold={focused}
|
|
||||||
dimColor={!focused}
|
|
||||||
color={focused ? color : undefined}
|
|
||||||
inverse={focused}
|
|
||||||
>
|
|
||||||
{focused ? ` ${companion.name} ` : companion.name}
|
{focused ? ` ${companion.name} ` : companion.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
|
|
||||||
if (!reaction) {
|
if (!reaction) {
|
||||||
return <Box paddingX={1}>{spriteColumn}</Box>
|
return <Box paddingX={1}>{spriteColumn}</Box>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fullscreen: bubble renders separately via CompanionFloatingBubble in
|
// Fullscreen: bubble renders separately via CompanionFloatingBubble in
|
||||||
@@ -288,19 +240,14 @@ export function CompanionSprite(): React.ReactNode {
|
|||||||
// Non-fullscreen: bubble sits inline beside the sprite (input shrinks)
|
// Non-fullscreen: bubble sits inline beside the sprite (input shrinks)
|
||||||
// because floating into Static scrollback can't be cleared.
|
// because floating into Static scrollback can't be cleared.
|
||||||
if (isFullscreenActive()) {
|
if (isFullscreenActive()) {
|
||||||
return <Box paddingX={1}>{spriteColumn}</Box>
|
return <Box paddingX={1}>{spriteColumn}</Box>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="row" alignItems="flex-end" paddingX={1} flexShrink={0}>
|
<Box flexDirection="row" alignItems="flex-end" paddingX={1} flexShrink={0}>
|
||||||
<SpeechBubble
|
<SpeechBubble text={reaction} color={color} fading={fading} tail="right" />
|
||||||
text={reaction}
|
|
||||||
color={color}
|
|
||||||
fading={fading}
|
|
||||||
tail="right"
|
|
||||||
/>
|
|
||||||
{spriteColumn}
|
{spriteColumn}
|
||||||
</Box>
|
</Box>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Floating bubble overlay for fullscreen mode. Mounted in FullscreenLayout's
|
// Floating bubble overlay for fullscreen mode. Mounted in FullscreenLayout's
|
||||||
@@ -308,33 +255,29 @@ export function CompanionSprite(): React.ReactNode {
|
|||||||
// the ScrollBox region. CompanionSprite owns the clear-after-10s timer; this
|
// the ScrollBox region. CompanionSprite owns the clear-after-10s timer; this
|
||||||
// just reads companionReaction and renders the fade.
|
// just reads companionReaction and renders the fade.
|
||||||
export function CompanionFloatingBubble(): React.ReactNode {
|
export function CompanionFloatingBubble(): React.ReactNode {
|
||||||
const reaction = useAppState(s => s.companionReaction)
|
const reaction = useAppState(s => s.companionReaction);
|
||||||
const [{ tick, forReaction }, setTick] = useState({
|
const [{ tick, forReaction }, setTick] = useState({
|
||||||
tick: 0,
|
tick: 0,
|
||||||
forReaction: reaction,
|
forReaction: reaction,
|
||||||
})
|
});
|
||||||
|
|
||||||
// Reset tick synchronously when reaction changes (not in useEffect, which
|
// Reset tick synchronously when reaction changes (not in useEffect, which
|
||||||
// runs post-render and would show one stale-faded frame). Storing the
|
// runs post-render and would show one stale-faded frame). Storing the
|
||||||
// reaction the tick is counting FOR alongside the tick itself means the
|
// reaction the tick is counting FOR alongside the tick itself means the
|
||||||
// fade computation never sees a tick from a previous reaction.
|
// fade computation never sees a tick from a previous reaction.
|
||||||
if (reaction !== forReaction) {
|
if (reaction !== forReaction) {
|
||||||
setTick({ tick: 0, forReaction: reaction })
|
setTick({ tick: 0, forReaction: reaction });
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!reaction) return
|
if (!reaction) return;
|
||||||
const timer = setInterval(
|
const timer = setInterval(set => set(s => ({ ...s, tick: s.tick + 1 })), TICK_MS, setTick);
|
||||||
set => set(s => ({ ...s, tick: s.tick + 1 })),
|
return () => clearInterval(timer);
|
||||||
TICK_MS,
|
}, [reaction]);
|
||||||
setTick,
|
|
||||||
)
|
|
||||||
return () => clearInterval(timer)
|
|
||||||
}, [reaction])
|
|
||||||
|
|
||||||
if (!feature('BUDDY') || !reaction) return null
|
if (!feature('BUDDY') || !reaction) return null;
|
||||||
const companion = getCompanion()
|
const companion = getCompanion();
|
||||||
if (!companion || getGlobalConfig().companionMuted) return null
|
if (!companion || getGlobalConfig().companionMuted) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SpeechBubble
|
<SpeechBubble
|
||||||
@@ -343,5 +286,5 @@ export function CompanionFloatingBubble(): React.ReactNode {
|
|||||||
fading={tick >= BUBBLE_SHOW - FADE_WINDOW}
|
fading={tick >= BUBBLE_SHOW - FADE_WINDOW}
|
||||||
tail="down"
|
tail="down"
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
import { feature } from 'bun:bundle'
|
import { feature } from 'bun:bundle';
|
||||||
import React, { useEffect } from 'react'
|
import React, { useEffect } from 'react';
|
||||||
import { useNotifications } from '../context/notifications.js'
|
import { useNotifications } from '../context/notifications.js';
|
||||||
import { Text } from '@anthropic/ink'
|
import { Text } from '@anthropic/ink';
|
||||||
import { getGlobalConfig } from '../utils/config.js'
|
import { getGlobalConfig } from '../utils/config.js';
|
||||||
import { getRainbowColor } from '../utils/thinking.js'
|
import { getRainbowColor } from '../utils/thinking.js';
|
||||||
|
|
||||||
// Local date, not UTC — 24h rolling wave across timezones. Sustained Twitter
|
// Local date, not UTC — 24h rolling wave across timezones. Sustained Twitter
|
||||||
// buzz instead of a single UTC-midnight spike, gentler on soul-gen load.
|
// buzz instead of a single UTC-midnight spike, gentler on soul-gen load.
|
||||||
// Teaser window: April 1-7, 2026 only. Command stays live forever after.
|
// Teaser window: April 1-7, 2026 only. Command stays live forever after.
|
||||||
export function isBuddyTeaserWindow(): boolean {
|
export function isBuddyTeaserWindow(): boolean {
|
||||||
if (process.env.USER_TYPE === 'ant') return true
|
if (process.env.USER_TYPE === 'ant') return true;
|
||||||
const d = new Date()
|
const d = new Date();
|
||||||
return d.getFullYear() === 2026 && d.getMonth() === 3 && d.getDate() <= 7
|
return d.getFullYear() === 2026 && d.getMonth() === 3 && d.getDate() <= 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isBuddyLive(): boolean {
|
export function isBuddyLive(): boolean {
|
||||||
if (process.env.USER_TYPE === 'ant') return true
|
if (process.env.USER_TYPE === 'ant') return true;
|
||||||
const d = new Date()
|
const d = new Date();
|
||||||
return (
|
return d.getFullYear() > 2026 || (d.getFullYear() === 2026 && d.getMonth() >= 3);
|
||||||
d.getFullYear() > 2026 || (d.getFullYear() === 2026 && d.getMonth() >= 3)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function RainbowText({ text }: { text: string }): React.ReactNode {
|
function RainbowText({ text }: { text: string }): React.ReactNode {
|
||||||
@@ -31,37 +29,35 @@ function RainbowText({ text }: { text: string }): React.ReactNode {
|
|||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rainbow /buddy teaser shown on startup when no companion hatched yet.
|
// Rainbow /buddy teaser shown on startup when no companion hatched yet.
|
||||||
// Idle presence and reactions are handled by CompanionSprite directly.
|
// Idle presence and reactions are handled by CompanionSprite directly.
|
||||||
export function useBuddyNotification(): void {
|
export function useBuddyNotification(): void {
|
||||||
const { addNotification, removeNotification } = useNotifications()
|
const { addNotification, removeNotification } = useNotifications();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!feature('BUDDY')) return
|
if (!feature('BUDDY')) return;
|
||||||
const config = getGlobalConfig()
|
const config = getGlobalConfig();
|
||||||
if (config.companion || !isBuddyTeaserWindow()) return
|
if (config.companion || !isBuddyTeaserWindow()) return;
|
||||||
addNotification({
|
addNotification({
|
||||||
key: 'buddy-teaser',
|
key: 'buddy-teaser',
|
||||||
jsx: <RainbowText text="/buddy" />,
|
jsx: <RainbowText text="/buddy" />,
|
||||||
priority: 'immediate',
|
priority: 'immediate',
|
||||||
timeoutMs: 15_000,
|
timeoutMs: 15_000,
|
||||||
})
|
});
|
||||||
return () => removeNotification('buddy-teaser')
|
return () => removeNotification('buddy-teaser');
|
||||||
}, [addNotification, removeNotification])
|
}, [addNotification, removeNotification]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findBuddyTriggerPositions(
|
export function findBuddyTriggerPositions(text: string): Array<{ start: number; end: number }> {
|
||||||
text: string,
|
if (!feature('BUDDY')) return [];
|
||||||
): Array<{ start: number; end: number }> {
|
const triggers: Array<{ start: number; end: number }> = [];
|
||||||
if (!feature('BUDDY')) return []
|
const re = /\/buddy\b/g;
|
||||||
const triggers: Array<{ start: number; end: number }> = []
|
let m: RegExpExecArray | null;
|
||||||
const re = /\/buddy\b/g
|
|
||||||
let m: RegExpExecArray | null
|
|
||||||
while ((m = re.exec(text)) !== null) {
|
while ((m = re.exec(text)) !== null) {
|
||||||
triggers.push({ start: m.index, end: m.index + m[0].length })
|
triggers.push({ start: m.index, end: m.index + m[0].length });
|
||||||
}
|
}
|
||||||
return triggers
|
return triggers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,9 @@ export async function attachHandler(target: string | undefined): Promise<void> {
|
|||||||
const { TmuxEngine } = await import('./bg/engines/tmux.js')
|
const { TmuxEngine } = await import('./bg/engines/tmux.js')
|
||||||
const tmux = new TmuxEngine()
|
const tmux = new TmuxEngine()
|
||||||
if (!(await tmux.available())) {
|
if (!(await tmux.available())) {
|
||||||
console.error('tmux is no longer available. Cannot attach to tmux session.')
|
console.error(
|
||||||
|
'tmux is no longer available. Cannot attach to tmux session.',
|
||||||
|
)
|
||||||
process.exitCode = 1
|
process.exitCode = 1
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -301,7 +303,9 @@ export async function handleBgStart(args: string[]): Promise<void> {
|
|||||||
console.log(` Engine: ${result.engineUsed}`)
|
console.log(` Engine: ${result.engineUsed}`)
|
||||||
console.log(` Log: ${result.logPath}`)
|
console.log(` Log: ${result.logPath}`)
|
||||||
console.log()
|
console.log()
|
||||||
console.log(`Use \`claude daemon attach ${result.sessionName}\` to reconnect.`)
|
console.log(
|
||||||
|
`Use \`claude daemon attach ${result.sessionName}\` to reconnect.`,
|
||||||
|
)
|
||||||
console.log(`Use \`claude daemon status\` to check status.`)
|
console.log(`Use \`claude daemon status\` to check status.`)
|
||||||
console.log(`Use \`claude daemon kill ${result.sessionName}\` to stop.`)
|
console.log(`Use \`claude daemon kill ${result.sessionName}\` to stop.`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { spawn } from 'child_process'
|
import { spawn } from 'child_process'
|
||||||
import { openSync, closeSync, mkdirSync } from 'fs'
|
import { openSync, closeSync, mkdirSync } from 'fs'
|
||||||
import { dirname } from 'path'
|
import { dirname } from 'path'
|
||||||
import type { BgEngine, BgStartOptions, BgStartResult, SessionEntry } from '../engine.js'
|
import type {
|
||||||
|
BgEngine,
|
||||||
|
BgStartOptions,
|
||||||
|
BgStartResult,
|
||||||
|
SessionEntry,
|
||||||
|
} from '../engine.js'
|
||||||
import { tailLog } from '../tail.js'
|
import { tailLog } from '../tail.js'
|
||||||
|
|
||||||
export class DetachedEngine implements BgEngine {
|
export class DetachedEngine implements BgEngine {
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
export type { BgEngine, BgStartOptions, BgStartResult, SessionEntry } from '../engine.js'
|
export type {
|
||||||
|
BgEngine,
|
||||||
|
BgStartOptions,
|
||||||
|
BgStartResult,
|
||||||
|
SessionEntry,
|
||||||
|
} from '../engine.js'
|
||||||
|
|
||||||
export async function selectEngine(): Promise<import('../engine.js').BgEngine> {
|
export async function selectEngine(): Promise<import('../engine.js').BgEngine> {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import { spawnSync } from 'child_process'
|
import { spawnSync } from 'child_process'
|
||||||
import { execFileNoThrow } from '../../../utils/execFileNoThrow.js'
|
import { execFileNoThrow } from '../../../utils/execFileNoThrow.js'
|
||||||
import { quote } from '../../../utils/bash/shellQuote.js'
|
import { quote } from '../../../utils/bash/shellQuote.js'
|
||||||
import type { BgEngine, BgStartOptions, BgStartResult, SessionEntry } from '../engine.js'
|
import type {
|
||||||
|
BgEngine,
|
||||||
|
BgStartOptions,
|
||||||
|
BgStartResult,
|
||||||
|
SessionEntry,
|
||||||
|
} from '../engine.js'
|
||||||
|
|
||||||
export class TmuxEngine implements BgEngine {
|
export class TmuxEngine implements BgEngine {
|
||||||
readonly name = 'tmux' as const
|
readonly name = 'tmux' as const
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
/** Write an error message to stderr (if given) and exit with code 1. */
|
/** Write an error message to stderr (if given) and exit with code 1. */
|
||||||
export function cliError(msg?: string): never {
|
export function cliError(msg?: string): never {
|
||||||
// biome-ignore lint/suspicious/noConsole: centralized CLI error output
|
|
||||||
if (msg) console.error(msg)
|
if (msg) console.error(msg)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
return undefined as never
|
return undefined as never
|
||||||
|
|||||||
@@ -59,12 +59,9 @@ export async function agentsHandler(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lines.length === 0) {
|
if (lines.length === 0) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('No agents found.')
|
console.log('No agents found.')
|
||||||
} else {
|
} else {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(`${totalActive} active agents\n`)
|
console.log(`${totalActive} active agents\n`)
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(lines.join('\n').trimEnd())
|
console.log(lines.join('\n').trimEnd())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,9 @@ export async function authLogin({
|
|||||||
|
|
||||||
const orgResult = await validateForceLoginOrg()
|
const orgResult = await validateForceLoginOrg()
|
||||||
if (!orgResult.valid) {
|
if (!orgResult.valid) {
|
||||||
process.stderr.write((orgResult as { valid: false; message: string }).message + '\n')
|
process.stderr.write(
|
||||||
|
(orgResult as { valid: false; message: string }).message + '\n',
|
||||||
|
)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +211,9 @@ export async function authLogin({
|
|||||||
|
|
||||||
const orgResult = await validateForceLoginOrg()
|
const orgResult = await validateForceLoginOrg()
|
||||||
if (!orgResult.valid) {
|
if (!orgResult.valid) {
|
||||||
process.stderr.write((orgResult as { valid: false; message: string }).message + '\n')
|
process.stderr.write(
|
||||||
|
(orgResult as { valid: false; message: string }).message + '\n',
|
||||||
|
)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,203 +3,163 @@
|
|||||||
* These are dynamically imported only when the corresponding `claude mcp *` command runs.
|
* These are dynamically imported only when the corresponding `claude mcp *` command runs.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { stat } from 'fs/promises'
|
import { stat } from 'fs/promises';
|
||||||
import pMap from 'p-map'
|
import pMap from 'p-map';
|
||||||
import { cwd } from 'process'
|
import { cwd } from 'process';
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { MCPServerDesktopImportDialog } from '../../components/MCPServerDesktopImportDialog.js'
|
import { MCPServerDesktopImportDialog } from '../../components/MCPServerDesktopImportDialog.js';
|
||||||
import { wrappedRender as render } from '@anthropic/ink'
|
import { wrappedRender as render } from '@anthropic/ink';
|
||||||
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js'
|
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js';
|
||||||
import {
|
import {
|
||||||
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
logEvent,
|
logEvent,
|
||||||
} from '../../services/analytics/index.js'
|
} from '../../services/analytics/index.js';
|
||||||
import {
|
import {
|
||||||
clearMcpClientConfig,
|
clearMcpClientConfig,
|
||||||
clearServerTokensFromLocalStorage,
|
clearServerTokensFromLocalStorage,
|
||||||
getMcpClientConfig,
|
getMcpClientConfig,
|
||||||
readClientSecret,
|
readClientSecret,
|
||||||
saveMcpClientSecret,
|
saveMcpClientSecret,
|
||||||
} from '../../services/mcp/auth.js'
|
} from '../../services/mcp/auth.js';
|
||||||
import {
|
import { connectToServer, getMcpServerConnectionBatchSize } from '../../services/mcp/client.js';
|
||||||
connectToServer,
|
|
||||||
getMcpServerConnectionBatchSize,
|
|
||||||
} from '../../services/mcp/client.js'
|
|
||||||
import {
|
import {
|
||||||
addMcpConfig,
|
addMcpConfig,
|
||||||
getAllMcpConfigs,
|
getAllMcpConfigs,
|
||||||
getMcpConfigByName,
|
getMcpConfigByName,
|
||||||
getMcpConfigsByScope,
|
getMcpConfigsByScope,
|
||||||
removeMcpConfig,
|
removeMcpConfig,
|
||||||
} from '../../services/mcp/config.js'
|
} from '../../services/mcp/config.js';
|
||||||
import type {
|
import type { ConfigScope, ScopedMcpServerConfig } from '../../services/mcp/types.js';
|
||||||
ConfigScope,
|
import { describeMcpConfigFilePath, ensureConfigScope, getScopeLabel } from '../../services/mcp/utils.js';
|
||||||
ScopedMcpServerConfig,
|
import { AppStateProvider } from '../../state/AppState.js';
|
||||||
} from '../../services/mcp/types.js'
|
import { getCurrentProjectConfig, getGlobalConfig, saveCurrentProjectConfig } from '../../utils/config.js';
|
||||||
import {
|
import { isFsInaccessible } from '../../utils/errors.js';
|
||||||
describeMcpConfigFilePath,
|
import { gracefulShutdown } from '../../utils/gracefulShutdown.js';
|
||||||
ensureConfigScope,
|
import { safeParseJSON } from '../../utils/json.js';
|
||||||
getScopeLabel,
|
import { getPlatform } from '../../utils/platform.js';
|
||||||
} from '../../services/mcp/utils.js'
|
import { cliError, cliOk } from '../exit.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(
|
async function checkMcpServerHealth(name: string, server: ScopedMcpServerConfig): Promise<string> {
|
||||||
name: string,
|
|
||||||
server: ScopedMcpServerConfig,
|
|
||||||
): Promise<string> {
|
|
||||||
try {
|
try {
|
||||||
const result = await connectToServer(name, server)
|
const result = await connectToServer(name, server);
|
||||||
if (result.type === 'connected') {
|
if (result.type === 'connected') {
|
||||||
return '✓ Connected'
|
return '✓ Connected';
|
||||||
} else if (result.type === 'needs-auth') {
|
} else if (result.type === 'needs-auth') {
|
||||||
return '! Needs authentication'
|
return '! Needs authentication';
|
||||||
} else {
|
} else {
|
||||||
return '✗ Failed to connect'
|
return '✗ Failed to connect';
|
||||||
}
|
}
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
return '✗ Connection error'
|
return '✗ Connection error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mcp serve (lines 4512–4532)
|
// mcp serve (lines 4512–4532)
|
||||||
export async function mcpServeHandler({
|
export async function mcpServeHandler({ debug, verbose }: { debug?: boolean; verbose?: boolean }): Promise<void> {
|
||||||
debug,
|
const providedCwd = cwd();
|
||||||
verbose,
|
logEvent('tengu_mcp_start', {});
|
||||||
}: {
|
|
||||||
debug?: boolean
|
|
||||||
verbose?: boolean
|
|
||||||
}): Promise<void> {
|
|
||||||
const providedCwd = cwd()
|
|
||||||
logEvent('tengu_mcp_start', {})
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await stat(providedCwd)
|
await stat(providedCwd);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isFsInaccessible(error)) {
|
if (isFsInaccessible(error)) {
|
||||||
cliError(`Error: Directory ${providedCwd} does not exist`)
|
cliError(`Error: Directory ${providedCwd} does not exist`);
|
||||||
}
|
}
|
||||||
throw error
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { setup } = await import('../../setup.js')
|
const { setup } = await import('../../setup.js');
|
||||||
await setup(providedCwd, 'default', false, false, undefined, false)
|
await setup(providedCwd, 'default', false, false, undefined, false);
|
||||||
const { startMCPServer } = await import('../../entrypoints/mcp.js')
|
const { startMCPServer } = await import('../../entrypoints/mcp.js');
|
||||||
await startMCPServer(providedCwd, debug ?? false, verbose ?? false)
|
await startMCPServer(providedCwd, debug ?? false, verbose ?? false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
cliError(`Error: Failed to start MCP server: ${error}`)
|
cliError(`Error: Failed to start MCP server: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mcp remove (lines 4545–4635)
|
// mcp remove (lines 4545–4635)
|
||||||
export async function mcpRemoveHandler(
|
export async function mcpRemoveHandler(name: string, options: { scope?: string }): Promise<void> {
|
||||||
name: string,
|
|
||||||
options: { scope?: string },
|
|
||||||
): Promise<void> {
|
|
||||||
// Look up config before removing so we can clean up secure storage
|
// Look up config before removing so we can clean up secure storage
|
||||||
const serverBeforeRemoval = getMcpConfigByName(name)
|
const serverBeforeRemoval = getMcpConfigByName(name);
|
||||||
|
|
||||||
const cleanupSecureStorage = () => {
|
const cleanupSecureStorage = () => {
|
||||||
if (
|
if (serverBeforeRemoval && (serverBeforeRemoval.type === 'sse' || serverBeforeRemoval.type === 'http')) {
|
||||||
serverBeforeRemoval &&
|
clearServerTokensFromLocalStorage(name, serverBeforeRemoval);
|
||||||
(serverBeforeRemoval.type === 'sse' ||
|
clearMcpClientConfig(name, serverBeforeRemoval);
|
||||||
serverBeforeRemoval.type === 'http')
|
|
||||||
) {
|
|
||||||
clearServerTokensFromLocalStorage(name, serverBeforeRemoval)
|
|
||||||
clearMcpClientConfig(name, serverBeforeRemoval)
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (options.scope) {
|
if (options.scope) {
|
||||||
const scope = ensureConfigScope(options.scope)
|
const scope = ensureConfigScope(options.scope);
|
||||||
logEvent('tengu_mcp_delete', {
|
logEvent('tengu_mcp_delete', {
|
||||||
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
scope:
|
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
});
|
||||||
})
|
|
||||||
|
|
||||||
await removeMcpConfig(name, scope)
|
await removeMcpConfig(name, scope);
|
||||||
cleanupSecureStorage()
|
cleanupSecureStorage();
|
||||||
process.stdout.write(`Removed MCP server ${name} from ${scope} config\n`)
|
process.stdout.write(`Removed MCP server ${name} from ${scope} config\n`);
|
||||||
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`)
|
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no scope specified, check where the server exists
|
// If no scope specified, check where the server exists
|
||||||
const projectConfig = getCurrentProjectConfig()
|
const projectConfig = getCurrentProjectConfig();
|
||||||
const globalConfig = getGlobalConfig()
|
const globalConfig = getGlobalConfig();
|
||||||
|
|
||||||
// Check if server exists in project scope (.mcp.json)
|
// Check if server exists in project scope (.mcp.json)
|
||||||
const { servers: projectServers } = getMcpConfigsByScope('project')
|
const { servers: projectServers } = getMcpConfigsByScope('project');
|
||||||
const mcpJsonExists = !!projectServers[name]
|
const mcpJsonExists = !!projectServers[name];
|
||||||
|
|
||||||
// Count how many scopes contain this server
|
// Count how many scopes contain this server
|
||||||
const scopes: Array<Exclude<ConfigScope, 'dynamic'>> = []
|
const scopes: Array<Exclude<ConfigScope, 'dynamic'>> = [];
|
||||||
if (projectConfig.mcpServers?.[name]) scopes.push('local')
|
if (projectConfig.mcpServers?.[name]) scopes.push('local');
|
||||||
if (mcpJsonExists) scopes.push('project')
|
if (mcpJsonExists) scopes.push('project');
|
||||||
if (globalConfig.mcpServers?.[name]) scopes.push('user')
|
if (globalConfig.mcpServers?.[name]) scopes.push('user');
|
||||||
|
|
||||||
if (scopes.length === 0) {
|
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) {
|
} else if (scopes.length === 1) {
|
||||||
// Server exists in only one scope, remove it
|
// Server exists in only one scope, remove it
|
||||||
const scope = scopes[0]!
|
const scope = scopes[0]!;
|
||||||
logEvent('tengu_mcp_delete', {
|
logEvent('tengu_mcp_delete', {
|
||||||
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
scope:
|
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
});
|
||||||
})
|
|
||||||
|
|
||||||
await removeMcpConfig(name, scope)
|
await removeMcpConfig(name, scope);
|
||||||
cleanupSecureStorage()
|
cleanupSecureStorage();
|
||||||
process.stdout.write(
|
process.stdout.write(`Removed MCP server "${name}" from ${scope} config\n`);
|
||||||
`Removed MCP server "${name}" from ${scope} config\n`,
|
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`);
|
||||||
)
|
|
||||||
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`)
|
|
||||||
} else {
|
} else {
|
||||||
// Server exists in multiple scopes
|
// 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 => {
|
scopes.forEach(scope => {
|
||||||
process.stderr.write(
|
process.stderr.write(` - ${getScopeLabel(scope)} (${describeMcpConfigFilePath(scope)})\n`);
|
||||||
` - ${getScopeLabel(scope)} (${describeMcpConfigFilePath(scope)})\n`,
|
});
|
||||||
)
|
process.stderr.write('\nTo remove from a specific scope, use:\n');
|
||||||
})
|
|
||||||
process.stderr.write('\nTo remove from a specific scope, use:\n')
|
|
||||||
scopes.forEach(scope => {
|
scopes.forEach(scope => {
|
||||||
process.stderr.write(` claude mcp remove "${name}" -s ${scope}\n`)
|
process.stderr.write(` claude mcp remove "${name}" -s ${scope}\n`);
|
||||||
})
|
});
|
||||||
cliError()
|
cliError();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
cliError((error as Error).message)
|
cliError((error as Error).message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mcp list (lines 4641–4688)
|
// mcp list (lines 4641–4688)
|
||||||
export async function mcpListHandler(): Promise<void> {
|
export async function mcpListHandler(): Promise<void> {
|
||||||
logEvent('tengu_mcp_list', {})
|
logEvent('tengu_mcp_list', {});
|
||||||
const { servers: configs } = await getAllMcpConfigs()
|
const { servers: configs } = await getAllMcpConfigs();
|
||||||
if (Object.keys(configs).length === 0) {
|
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 {
|
} 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
|
// Check servers concurrently
|
||||||
const entries = Object.entries(configs)
|
const entries = Object.entries(configs);
|
||||||
const results = await pMap(
|
const results = await pMap(
|
||||||
entries,
|
entries,
|
||||||
async ([name, server]) => ({
|
async ([name, server]) => ({
|
||||||
@@ -208,127 +168,100 @@ export async function mcpListHandler(): Promise<void> {
|
|||||||
status: await checkMcpServerHealth(name, server),
|
status: await checkMcpServerHealth(name, server),
|
||||||
}),
|
}),
|
||||||
{ concurrency: getMcpServerConnectionBatchSize() },
|
{ concurrency: getMcpServerConnectionBatchSize() },
|
||||||
)
|
);
|
||||||
|
|
||||||
for (const { name, server, status } of results) {
|
for (const { name, server, status } of results) {
|
||||||
// Intentionally excluding sse-ide servers here since they're internal
|
// Intentionally excluding sse-ide servers here since they're internal
|
||||||
if (server.type === 'sse') {
|
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') {
|
} 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') {
|
} 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') {
|
} else if (!server.type || server.type === 'stdio') {
|
||||||
const stdioServer = server as { command: string; args: string[]; type?: string }
|
const stdioServer = server as { command: string; args: string[]; type?: string };
|
||||||
const args = Array.isArray(stdioServer.args) ? stdioServer.args : []
|
const args = Array.isArray(stdioServer.args) ? stdioServer.args : [];
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
console.log(`${name}: ${stdioServer.command} ${args.join(' ')} - ${status}`);
|
||||||
console.log(`${name}: ${stdioServer.command} ${args.join(' ')} - ${status}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Use gracefulShutdown to properly clean up MCP server connections
|
// Use gracefulShutdown to properly clean up MCP server connections
|
||||||
// (process.exit bypasses cleanup handlers, leaving child processes orphaned)
|
// (process.exit bypasses cleanup handlers, leaving child processes orphaned)
|
||||||
await gracefulShutdown(0)
|
await gracefulShutdown(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mcp get (lines 4694–4786)
|
// mcp get (lines 4694–4786)
|
||||||
export async function mcpGetHandler(name: string): Promise<void> {
|
export async function mcpGetHandler(name: string): Promise<void> {
|
||||||
logEvent('tengu_mcp_get', {
|
logEvent('tengu_mcp_get', {
|
||||||
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
})
|
});
|
||||||
const server = getMcpConfigByName(name)
|
const server = getMcpConfigByName(name);
|
||||||
if (!server) {
|
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}:`)
|
console.log(` Scope: ${getScopeLabel(server.scope)}`);
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Scope: ${getScopeLabel(server.scope)}`)
|
|
||||||
|
|
||||||
// Check server health
|
// 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
|
// Intentionally excluding sse-ide servers here since they're internal
|
||||||
if (server.type === 'sse') {
|
if (server.type === 'sse') {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
console.log(` Type: sse`);
|
||||||
console.log(` Type: sse`)
|
console.log(` URL: ${server.url}`);
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` URL: ${server.url}`)
|
|
||||||
if (server.headers) {
|
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)) {
|
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) {
|
if (server.oauth?.clientId || server.oauth?.callbackPort) {
|
||||||
const parts: string[] = []
|
const parts: string[] = [];
|
||||||
if (server.oauth.clientId) {
|
if (server.oauth.clientId) {
|
||||||
parts.push('client_id configured')
|
parts.push('client_id configured');
|
||||||
const clientConfig = getMcpClientConfig(name, server)
|
const clientConfig = getMcpClientConfig(name, server);
|
||||||
if (clientConfig?.clientSecret) parts.push('client_secret configured')
|
if (clientConfig?.clientSecret) parts.push('client_secret configured');
|
||||||
}
|
}
|
||||||
if (server.oauth.callbackPort)
|
if (server.oauth.callbackPort) parts.push(`callback_port ${server.oauth.callbackPort}`);
|
||||||
parts.push(`callback_port ${server.oauth.callbackPort}`)
|
console.log(` OAuth: ${parts.join(', ')}`);
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` OAuth: ${parts.join(', ')}`)
|
|
||||||
}
|
}
|
||||||
} else if (server.type === 'http') {
|
} else if (server.type === 'http') {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
console.log(` Type: http`);
|
||||||
console.log(` Type: http`)
|
console.log(` URL: ${server.url}`);
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` URL: ${server.url}`)
|
|
||||||
if (server.headers) {
|
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)) {
|
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) {
|
if (server.oauth?.clientId || server.oauth?.callbackPort) {
|
||||||
const parts: string[] = []
|
const parts: string[] = [];
|
||||||
if (server.oauth.clientId) {
|
if (server.oauth.clientId) {
|
||||||
parts.push('client_id configured')
|
parts.push('client_id configured');
|
||||||
const clientConfig = getMcpClientConfig(name, server)
|
const clientConfig = getMcpClientConfig(name, server);
|
||||||
if (clientConfig?.clientSecret) parts.push('client_secret configured')
|
if (clientConfig?.clientSecret) parts.push('client_secret configured');
|
||||||
}
|
}
|
||||||
if (server.oauth.callbackPort)
|
if (server.oauth.callbackPort) parts.push(`callback_port ${server.oauth.callbackPort}`);
|
||||||
parts.push(`callback_port ${server.oauth.callbackPort}`)
|
console.log(` OAuth: ${parts.join(', ')}`);
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` OAuth: ${parts.join(', ')}`)
|
|
||||||
}
|
}
|
||||||
} else if (server.type === 'stdio') {
|
} else if (server.type === 'stdio') {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
console.log(` Type: stdio`);
|
||||||
console.log(` Type: stdio`)
|
console.log(` Command: ${server.command}`);
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
const args = Array.isArray(server.args) ? server.args : [];
|
||||||
console.log(` Command: ${server.command}`)
|
console.log(` Args: ${args.join(' ')}`);
|
||||||
const args = Array.isArray(server.args) ? server.args : []
|
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Args: ${args.join(' ')}`)
|
|
||||||
if (server.env) {
|
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)) {
|
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
|
// Use gracefulShutdown to properly clean up MCP server connections
|
||||||
// (process.exit bypasses cleanup handlers, leaving child processes orphaned)
|
// (process.exit bypasses cleanup handlers, leaving child processes orphaned)
|
||||||
await gracefulShutdown(0)
|
await gracefulShutdown(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// mcp add-json (lines 4801–4870)
|
// mcp add-json (lines 4801–4870)
|
||||||
@@ -338,8 +271,8 @@ export async function mcpAddJsonHandler(
|
|||||||
options: { scope?: string; clientSecret?: true },
|
options: { scope?: string; clientSecret?: true },
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const scope = ensureConfigScope(options.scope)
|
const scope = ensureConfigScope(options.scope);
|
||||||
const parsedJson = safeParseJSON(json)
|
const parsedJson = safeParseJSON(json);
|
||||||
|
|
||||||
// Read secret before writing config so cancellation doesn't leave partial state
|
// Read secret before writing config so cancellation doesn't leave partial state
|
||||||
const needsSecret =
|
const needsSecret =
|
||||||
@@ -353,15 +286,15 @@ export async function mcpAddJsonHandler(
|
|||||||
'oauth' in parsedJson &&
|
'oauth' in parsedJson &&
|
||||||
parsedJson.oauth &&
|
parsedJson.oauth &&
|
||||||
typeof parsedJson.oauth === 'object' &&
|
typeof parsedJson.oauth === 'object' &&
|
||||||
'clientId' in parsedJson.oauth
|
'clientId' in parsedJson.oauth;
|
||||||
const clientSecret = needsSecret ? await readClientSecret() : undefined
|
const clientSecret = needsSecret ? await readClientSecret() : undefined;
|
||||||
|
|
||||||
await addMcpConfig(name, parsedJson, scope)
|
await addMcpConfig(name, parsedJson, scope);
|
||||||
|
|
||||||
const transportType =
|
const transportType =
|
||||||
parsedJson && typeof parsedJson === 'object' && 'type' in parsedJson
|
parsedJson && typeof parsedJson === 'object' && 'type' in parsedJson
|
||||||
? String(parsedJson.type || 'stdio')
|
? String(parsedJson.type || 'stdio')
|
||||||
: 'stdio'
|
: 'stdio';
|
||||||
|
|
||||||
if (
|
if (
|
||||||
clientSecret &&
|
clientSecret &&
|
||||||
@@ -372,53 +305,38 @@ export async function mcpAddJsonHandler(
|
|||||||
'url' in parsedJson &&
|
'url' in parsedJson &&
|
||||||
typeof parsedJson.url === 'string'
|
typeof parsedJson.url === 'string'
|
||||||
) {
|
) {
|
||||||
saveMcpClientSecret(
|
saveMcpClientSecret(name, { type: parsedJson.type, url: parsedJson.url }, clientSecret);
|
||||||
name,
|
|
||||||
{ type: parsedJson.type, url: parsedJson.url },
|
|
||||||
clientSecret,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logEvent('tengu_mcp_add', {
|
logEvent('tengu_mcp_add', {
|
||||||
scope:
|
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
source: 'json' 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,
|
type: transportType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
})
|
});
|
||||||
|
|
||||||
cliOk(`Added ${transportType} MCP server ${name} to ${scope} config`)
|
cliOk(`Added ${transportType} MCP server ${name} to ${scope} config`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
cliError((error as Error).message)
|
cliError((error as Error).message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mcp add-from-claude-desktop (lines 4881–4927)
|
// mcp add-from-claude-desktop (lines 4881–4927)
|
||||||
export async function mcpAddFromDesktopHandler(options: {
|
export async function mcpAddFromDesktopHandler(options: { scope?: string }): Promise<void> {
|
||||||
scope?: string
|
|
||||||
}): Promise<void> {
|
|
||||||
try {
|
try {
|
||||||
const scope = ensureConfigScope(options.scope)
|
const scope = ensureConfigScope(options.scope);
|
||||||
const platform = getPlatform()
|
const platform = getPlatform();
|
||||||
|
|
||||||
logEvent('tengu_mcp_add', {
|
logEvent('tengu_mcp_add', {
|
||||||
scope:
|
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
platform: platform as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
platform:
|
source: 'desktop' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
||||||
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(
|
const { readClaudeDesktopMcpServers } = await import('../../utils/claudeDesktop.js');
|
||||||
'../../utils/claudeDesktop.js'
|
const servers = await readClaudeDesktopMcpServers();
|
||||||
)
|
|
||||||
const servers = await readClaudeDesktopMcpServers()
|
|
||||||
|
|
||||||
if (Object.keys(servers).length === 0) {
|
if (Object.keys(servers).length === 0) {
|
||||||
cliOk(
|
cliOk('No MCP servers found in Claude Desktop configuration or configuration file does not exist.');
|
||||||
'No MCP servers found in Claude Desktop configuration or configuration file does not exist.',
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { unmount } = await render(
|
const { unmount } = await render(
|
||||||
@@ -428,29 +346,29 @@ export async function mcpAddFromDesktopHandler(options: {
|
|||||||
servers={servers}
|
servers={servers}
|
||||||
scope={scope}
|
scope={scope}
|
||||||
onDone={() => {
|
onDone={() => {
|
||||||
unmount()
|
unmount();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</KeybindingSetup>
|
</KeybindingSetup>
|
||||||
</AppStateProvider>,
|
</AppStateProvider>,
|
||||||
{ exitOnCtrlC: true },
|
{ exitOnCtrlC: true },
|
||||||
)
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
cliError((error as Error).message)
|
cliError((error as Error).message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mcp reset-project-choices (lines 4935–4952)
|
// mcp reset-project-choices (lines 4935–4952)
|
||||||
export async function mcpResetChoicesHandler(): Promise<void> {
|
export async function mcpResetChoicesHandler(): Promise<void> {
|
||||||
logEvent('tengu_mcp_reset_mcpjson_choices', {})
|
logEvent('tengu_mcp_reset_mcpjson_choices', {});
|
||||||
saveCurrentProjectConfig(current => ({
|
saveCurrentProjectConfig(current => ({
|
||||||
...current,
|
...current,
|
||||||
enabledMcpjsonServers: [],
|
enabledMcpjsonServers: [],
|
||||||
disabledMcpjsonServers: [],
|
disabledMcpjsonServers: [],
|
||||||
enableAllProjectMcpServers: false,
|
enableAllProjectMcpServers: false,
|
||||||
}))
|
}));
|
||||||
cliOk(
|
cliOk(
|
||||||
'All project-scoped (.mcp.json) server approvals and rejections have been reset.\n' +
|
'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.',
|
'You will be prompted for approval next time you start Claude Code.',
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,27 +72,21 @@ export function handleMarketplaceError(error: unknown, action: string): never {
|
|||||||
|
|
||||||
function printValidationResult(result: ValidationResult): void {
|
function printValidationResult(result: ValidationResult): void {
|
||||||
if (result.errors.length > 0) {
|
if (result.errors.length > 0) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(
|
console.log(
|
||||||
`${figures.cross} Found ${result.errors.length} ${plural(result.errors.length, 'error')}:\n`,
|
`${figures.cross} Found ${result.errors.length} ${plural(result.errors.length, 'error')}:\n`,
|
||||||
)
|
)
|
||||||
result.errors.forEach(error => {
|
result.errors.forEach(error => {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` ${figures.pointer} ${error.path}: ${error.message}`)
|
console.log(` ${figures.pointer} ${error.path}: ${error.message}`)
|
||||||
})
|
})
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('')
|
console.log('')
|
||||||
}
|
}
|
||||||
if (result.warnings.length > 0) {
|
if (result.warnings.length > 0) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(
|
console.log(
|
||||||
`${figures.warning} Found ${result.warnings.length} ${plural(result.warnings.length, 'warning')}:\n`,
|
`${figures.warning} Found ${result.warnings.length} ${plural(result.warnings.length, 'warning')}:\n`,
|
||||||
)
|
)
|
||||||
result.warnings.forEach(warning => {
|
result.warnings.forEach(warning => {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` ${figures.pointer} ${warning.path}: ${warning.message}`)
|
console.log(` ${figures.pointer} ${warning.path}: ${warning.message}`)
|
||||||
})
|
})
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('')
|
console.log('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +100,6 @@ export async function pluginValidateHandler(
|
|||||||
try {
|
try {
|
||||||
const result = await validateManifest(manifestPath)
|
const result = await validateManifest(manifestPath)
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(`Validating ${result.fileType} manifest: ${result.filePath}\n`)
|
console.log(`Validating ${result.fileType} manifest: ${result.filePath}\n`)
|
||||||
printValidationResult(result)
|
printValidationResult(result)
|
||||||
|
|
||||||
@@ -120,7 +113,6 @@ export async function pluginValidateHandler(
|
|||||||
if (basename(manifestDir) === '.claude-plugin') {
|
if (basename(manifestDir) === '.claude-plugin') {
|
||||||
contentResults = await validatePluginContents(dirname(manifestDir))
|
contentResults = await validatePluginContents(dirname(manifestDir))
|
||||||
for (const r of contentResults) {
|
for (const r of contentResults) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(`Validating ${r.fileType}: ${r.filePath}\n`)
|
console.log(`Validating ${r.fileType}: ${r.filePath}\n`)
|
||||||
printValidationResult(r)
|
printValidationResult(r)
|
||||||
}
|
}
|
||||||
@@ -139,13 +131,11 @@ export async function pluginValidateHandler(
|
|||||||
: `${figures.tick} Validation passed`,
|
: `${figures.tick} Validation passed`,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(`${figures.cross} Validation failed`)
|
console.log(`${figures.cross} Validation failed`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error)
|
logError(error)
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.error(
|
console.error(
|
||||||
`${figures.cross} Unexpected error during validation: ${errorMessage(error)}`,
|
`${figures.cross} Unexpected error during validation: ${errorMessage(error)}`,
|
||||||
)
|
)
|
||||||
@@ -358,7 +348,6 @@ export async function pluginListHandler(options: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pluginIds.length > 0) {
|
if (pluginIds.length > 0) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('Installed plugins:\n')
|
console.log('Installed plugins:\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,25 +372,18 @@ export async function pluginListHandler(options: {
|
|||||||
const version = installation.version || 'unknown'
|
const version = installation.version || 'unknown'
|
||||||
const scope = installation.scope
|
const scope = installation.scope
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` ${figures.pointer} ${pluginId}`)
|
console.log(` ${figures.pointer} ${pluginId}`)
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Version: ${version}`)
|
console.log(` Version: ${version}`)
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Scope: ${scope}`)
|
console.log(` Scope: ${scope}`)
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Status: ${status}`)
|
console.log(` Status: ${status}`)
|
||||||
for (const error of pluginErrors) {
|
for (const error of pluginErrors) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Error: ${getPluginErrorMessage(error)}`)
|
console.log(` Error: ${getPluginErrorMessage(error)}`)
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('')
|
console.log('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inlinePlugins.length > 0 || inlineLoadErrors.length > 0) {
|
if (inlinePlugins.length > 0 || inlineLoadErrors.length > 0) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('Session-only plugins (--plugin-dir):\n')
|
console.log('Session-only plugins (--plugin-dir):\n')
|
||||||
for (const p of inlinePlugins) {
|
for (const p of inlinePlugins) {
|
||||||
// Same dirName≠manifestName fallback as the JSON path above — error
|
// Same dirName≠manifestName fallback as the JSON path above — error
|
||||||
@@ -413,19 +395,13 @@ export async function pluginListHandler(options: {
|
|||||||
pErrors.length > 0
|
pErrors.length > 0
|
||||||
? `${figures.cross} loaded with errors`
|
? `${figures.cross} loaded with errors`
|
||||||
: `${figures.tick} loaded`
|
: `${figures.tick} loaded`
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` ${figures.pointer} ${p.source}`)
|
console.log(` ${figures.pointer} ${p.source}`)
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Version: ${p.manifest.version ?? 'unknown'}`)
|
console.log(` Version: ${p.manifest.version ?? 'unknown'}`)
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Path: ${p.path}`)
|
console.log(` Path: ${p.path}`)
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Status: ${status}`)
|
console.log(` Status: ${status}`)
|
||||||
for (const e of pErrors) {
|
for (const e of pErrors) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Error: ${getPluginErrorMessage(e)}`)
|
console.log(` Error: ${getPluginErrorMessage(e)}`)
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('')
|
console.log('')
|
||||||
}
|
}
|
||||||
// Path-level failures: no LoadedPlugin object exists. Show them so
|
// Path-level failures: no LoadedPlugin object exists. Show them so
|
||||||
@@ -433,7 +409,6 @@ export async function pluginListHandler(options: {
|
|||||||
for (const e of inlineLoadErrors.filter(e =>
|
for (const e of inlineLoadErrors.filter(e =>
|
||||||
e.source.startsWith('inline['),
|
e.source.startsWith('inline['),
|
||||||
)) {
|
)) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(
|
console.log(
|
||||||
` ${figures.pointer} ${e.source}: ${figures.cross} ${getPluginErrorMessage(e)}\n`,
|
` ${figures.pointer} ${e.source}: ${figures.cross} ${getPluginErrorMessage(e)}\n`,
|
||||||
)
|
)
|
||||||
@@ -489,12 +464,10 @@ export async function marketplaceAddHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('Adding marketplace...')
|
console.log('Adding marketplace...')
|
||||||
|
|
||||||
const { name, alreadyMaterialized, resolvedSource } =
|
const { name, alreadyMaterialized, resolvedSource } =
|
||||||
await addMarketplaceSource(marketplaceSource, message => {
|
await addMarketplaceSource(marketplaceSource, message => {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(message)
|
console.log(message)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -555,33 +528,25 @@ export async function marketplaceListHandler(options: {
|
|||||||
cliOk('No marketplaces configured')
|
cliOk('No marketplaces configured')
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('Configured marketplaces:\n')
|
console.log('Configured marketplaces:\n')
|
||||||
names.forEach(name => {
|
names.forEach(name => {
|
||||||
const marketplace = config[name]
|
const marketplace = config[name]
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` ${figures.pointer} ${name}`)
|
console.log(` ${figures.pointer} ${name}`)
|
||||||
|
|
||||||
if (marketplace?.source) {
|
if (marketplace?.source) {
|
||||||
const src = marketplace.source
|
const src = marketplace.source
|
||||||
if (src.source === 'github') {
|
if (src.source === 'github') {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Source: GitHub (${src.repo})`)
|
console.log(` Source: GitHub (${src.repo})`)
|
||||||
} else if (src.source === 'git') {
|
} else if (src.source === 'git') {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Source: Git (${src.url})`)
|
console.log(` Source: Git (${src.url})`)
|
||||||
} else if (src.source === 'url') {
|
} else if (src.source === 'url') {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Source: URL (${src.url})`)
|
console.log(` Source: URL (${src.url})`)
|
||||||
} else if (src.source === 'directory') {
|
} else if (src.source === 'directory') {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Source: Directory (${src.path})`)
|
console.log(` Source: Directory (${src.path})`)
|
||||||
} else if (src.source === 'file') {
|
} else if (src.source === 'file') {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(` Source: File (${src.path})`)
|
console.log(` Source: File (${src.path})`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log('')
|
console.log('')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -620,11 +585,9 @@ export async function marketplaceUpdateHandler(
|
|||||||
if (options.cowork) setUseCoworkPlugins(true)
|
if (options.cowork) setUseCoworkPlugins(true)
|
||||||
try {
|
try {
|
||||||
if (name) {
|
if (name) {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(`Updating marketplace: ${name}...`)
|
console.log(`Updating marketplace: ${name}...`)
|
||||||
|
|
||||||
await refreshMarketplace(name, message => {
|
await refreshMarketplace(name, message => {
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(message)
|
console.log(message)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -644,7 +607,6 @@ export async function marketplaceUpdateHandler(
|
|||||||
cliOk('No marketplaces configured')
|
cliOk('No marketplaces configured')
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|
||||||
console.log(`Updating ${marketplaceNames.length} marketplace(s)...`)
|
console.log(`Updating ${marketplaceNames.length} marketplace(s)...`)
|
||||||
|
|
||||||
await refreshAllMarketplaces()
|
await refreshAllMarketplaces()
|
||||||
|
|||||||
@@ -4,26 +4,24 @@
|
|||||||
*/
|
*/
|
||||||
/* eslint-disable custom-rules/no-process-exit -- CLI subcommand handlers intentionally exit */
|
/* eslint-disable custom-rules/no-process-exit -- CLI subcommand handlers intentionally exit */
|
||||||
|
|
||||||
import { cwd } from 'process'
|
import { cwd } from 'process';
|
||||||
import React from 'react'
|
import React from 'react';
|
||||||
import { WelcomeV2 } from '../../components/LogoV2/WelcomeV2.js'
|
import { WelcomeV2 } from '../../components/LogoV2/WelcomeV2.js';
|
||||||
import { useManagePlugins } from '../../hooks/useManagePlugins.js'
|
import { useManagePlugins } from '../../hooks/useManagePlugins.js';
|
||||||
import type { Root } from '@anthropic/ink'
|
import type { Root } from '@anthropic/ink';
|
||||||
import { Box, Text } from '@anthropic/ink'
|
import { Box, Text } from '@anthropic/ink';
|
||||||
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js'
|
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js';
|
||||||
import { logEvent } from '../../services/analytics/index.js'
|
import { logEvent } from '../../services/analytics/index.js';
|
||||||
import { MCPConnectionManager } from '../../services/mcp/MCPConnectionManager.js'
|
import { MCPConnectionManager } from '../../services/mcp/MCPConnectionManager.js';
|
||||||
import { AppStateProvider } from '../../state/AppState.js'
|
import { AppStateProvider } from '../../state/AppState.js';
|
||||||
import { onChangeAppState } from '../../state/onChangeAppState.js'
|
import { onChangeAppState } from '../../state/onChangeAppState.js';
|
||||||
import { isAnthropicAuthEnabled } from '../../utils/auth.js'
|
import { isAnthropicAuthEnabled } from '../../utils/auth.js';
|
||||||
|
|
||||||
export async function setupTokenHandler(root: Root): Promise<void> {
|
export async function setupTokenHandler(root: Root): Promise<void> {
|
||||||
logEvent('tengu_setup_token_command', {})
|
logEvent('tengu_setup_token_command', {});
|
||||||
|
|
||||||
const showAuthWarning = !isAnthropicAuthEnabled()
|
const showAuthWarning = !isAnthropicAuthEnabled();
|
||||||
const { ConsoleOAuthFlow } = await import(
|
const { ConsoleOAuthFlow } = await import('../../components/ConsoleOAuthFlow.js');
|
||||||
'../../components/ConsoleOAuthFlow.js'
|
|
||||||
)
|
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>(resolve => {
|
||||||
root.render(
|
root.render(
|
||||||
<AppStateProvider onChangeAppState={onChangeAppState}>
|
<AppStateProvider onChangeAppState={onChangeAppState}>
|
||||||
@@ -33,18 +31,16 @@ export async function setupTokenHandler(root: Root): Promise<void> {
|
|||||||
{showAuthWarning && (
|
{showAuthWarning && (
|
||||||
<Box flexDirection="column">
|
<Box flexDirection="column">
|
||||||
<Text color="warning">
|
<Text color="warning">
|
||||||
Warning: You already have authentication configured via
|
Warning: You already have authentication configured via environment variable or API key helper.
|
||||||
environment variable or API key helper.
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text color="warning">
|
<Text color="warning">
|
||||||
The setup-token command will create a new OAuth token which
|
The setup-token command will create a new OAuth token which you can use instead.
|
||||||
you can use instead.
|
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<ConsoleOAuthFlow
|
<ConsoleOAuthFlow
|
||||||
onDone={() => {
|
onDone={() => {
|
||||||
void resolve()
|
void resolve();
|
||||||
}}
|
}}
|
||||||
mode="setup-token"
|
mode="setup-token"
|
||||||
startingMessage="This will guide you through long-lived (1-year) auth token setup for your Claude account. Claude subscription required."
|
startingMessage="This will guide you through long-lived (1-year) auth token setup for your Claude account. Claude subscription required."
|
||||||
@@ -52,75 +48,63 @@ export async function setupTokenHandler(root: Root): Promise<void> {
|
|||||||
</Box>
|
</Box>
|
||||||
</KeybindingSetup>
|
</KeybindingSetup>
|
||||||
</AppStateProvider>,
|
</AppStateProvider>,
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
root.unmount()
|
root.unmount();
|
||||||
process.exit(0)
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DoctorWithPlugins wrapper + doctor handler
|
// DoctorWithPlugins wrapper + doctor handler
|
||||||
const DoctorLazy = React.lazy(() =>
|
const DoctorLazy = React.lazy(() => import('../../screens/Doctor.js').then(m => ({ default: m.Doctor })));
|
||||||
import('../../screens/Doctor.js').then(m => ({ default: m.Doctor })),
|
|
||||||
)
|
|
||||||
|
|
||||||
function DoctorWithPlugins({
|
function DoctorWithPlugins({ onDone }: { onDone: () => void }): React.ReactNode {
|
||||||
onDone,
|
useManagePlugins();
|
||||||
}: {
|
|
||||||
onDone: () => void
|
|
||||||
}): React.ReactNode {
|
|
||||||
useManagePlugins()
|
|
||||||
return (
|
return (
|
||||||
<React.Suspense fallback={null}>
|
<React.Suspense fallback={null}>
|
||||||
<DoctorLazy onDone={onDone} />
|
<DoctorLazy onDone={onDone} />
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doctorHandler(root: Root): Promise<void> {
|
export async function doctorHandler(root: Root): Promise<void> {
|
||||||
logEvent('tengu_doctor_command', {})
|
logEvent('tengu_doctor_command', {});
|
||||||
|
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>(resolve => {
|
||||||
root.render(
|
root.render(
|
||||||
<AppStateProvider>
|
<AppStateProvider>
|
||||||
<KeybindingSetup>
|
<KeybindingSetup>
|
||||||
<MCPConnectionManager
|
<MCPConnectionManager dynamicMcpConfig={undefined} isStrictMcpConfig={false}>
|
||||||
dynamicMcpConfig={undefined}
|
|
||||||
isStrictMcpConfig={false}
|
|
||||||
>
|
|
||||||
<DoctorWithPlugins
|
<DoctorWithPlugins
|
||||||
onDone={() => {
|
onDone={() => {
|
||||||
void resolve()
|
void resolve();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</MCPConnectionManager>
|
</MCPConnectionManager>
|
||||||
</KeybindingSetup>
|
</KeybindingSetup>
|
||||||
</AppStateProvider>,
|
</AppStateProvider>,
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
root.unmount()
|
root.unmount();
|
||||||
process.exit(0)
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// install handler
|
// install handler
|
||||||
export async function installHandler(
|
export async function installHandler(target: string | undefined, options: { force?: boolean }): Promise<void> {
|
||||||
target: string | undefined,
|
const { setup } = await import('../../setup.js');
|
||||||
options: { force?: boolean },
|
await setup(cwd(), 'default', false, false, undefined, false);
|
||||||
): Promise<void> {
|
const { install } = await import('../../commands/install.js');
|
||||||
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 => {
|
await new Promise<void>(resolve => {
|
||||||
const args: string[] = []
|
const args: string[] = [];
|
||||||
if (target) args.push(target)
|
if (target) args.push(target);
|
||||||
if (options.force) args.push('--force')
|
if (options.force) args.push('--force');
|
||||||
|
|
||||||
void install.call(
|
void install.call(
|
||||||
result => {
|
result => {
|
||||||
void resolve()
|
void resolve();
|
||||||
process.exit(result.includes('failed') ? 1 : 0)
|
process.exit(result.includes('failed') ? 1 : 0);
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
args,
|
args,
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type ask = any;
|
export type ask = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type installOAuthTokens = any;
|
export type installOAuthTokens = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type RemoteIO = any;
|
export type RemoteIO = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type StructuredIO = any;
|
export type StructuredIO = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type collectContextData = any;
|
export type collectContextData = any
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type SDKStatus = any;
|
export type SDKStatus = any
|
||||||
export type ModelInfo = any;
|
export type ModelInfo = any
|
||||||
export type SDKMessage = any;
|
export type SDKMessage = any
|
||||||
export type SDKUserMessage = any;
|
export type SDKUserMessage = any
|
||||||
export type SDKUserMessageReplay = any;
|
export type SDKUserMessageReplay = any
|
||||||
export type PermissionResult = any;
|
export type PermissionResult = any
|
||||||
export type McpServerConfigForProcessTransport = any;
|
export type McpServerConfigForProcessTransport = any
|
||||||
export type McpServerStatus = any;
|
export type McpServerStatus = any
|
||||||
export type RewindFilesResult = any;
|
export type RewindFilesResult = any
|
||||||
export type HookEvent = any;
|
export type HookEvent = any
|
||||||
export type HookInput = any;
|
export type HookInput = any
|
||||||
export type HookJSONOutput = any;
|
export type HookJSONOutput = any
|
||||||
export type PermissionUpdate = any;
|
export type PermissionUpdate = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type SDKControlElicitationResponseSchema = any;
|
export type SDKControlElicitationResponseSchema = any
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type StdoutMessage = any;
|
export type StdoutMessage = any
|
||||||
export type SDKControlInitializeRequest = any;
|
export type SDKControlInitializeRequest = any
|
||||||
export type SDKControlInitializeResponse = any;
|
export type SDKControlInitializeResponse = any
|
||||||
export type SDKControlRequest = any;
|
export type SDKControlRequest = any
|
||||||
export type SDKControlResponse = any;
|
export type SDKControlResponse = any
|
||||||
export type SDKControlMcpSetServersResponse = any;
|
export type SDKControlMcpSetServersResponse = any
|
||||||
export type SDKControlReloadPluginsResponse = any;
|
export type SDKControlReloadPluginsResponse = any
|
||||||
export type StdinMessage = any;
|
export type StdinMessage = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type CanUseToolFn = any;
|
export type CanUseToolFn = any
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type tryGenerateSuggestion = any;
|
export type tryGenerateSuggestion = any
|
||||||
export type logSuggestionOutcome = any;
|
export type logSuggestionOutcome = any
|
||||||
export type logSuggestionSuppressed = any;
|
export type logSuggestionSuppressed = any
|
||||||
export type PromptVariant = any;
|
export type PromptVariant = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type getFeatureValue_CACHED_MAY_BE_STALE = any;
|
export type getFeatureValue_CACHED_MAY_BE_STALE = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type logEvent = any;
|
export type logEvent = any
|
||||||
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = any;
|
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type isQualifiedForGrove = any;
|
export type isQualifiedForGrove = any
|
||||||
export type checkGroveForNonInteractive = any;
|
export type checkGroveForNonInteractive = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type EMPTY_USAGE = any;
|
export type EMPTY_USAGE = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type statusListeners = any;
|
export type statusListeners = any
|
||||||
export type ClaudeAILimits = any;
|
export type ClaudeAILimits = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type performMCPOAuthFlow = any;
|
export type performMCPOAuthFlow = any
|
||||||
export type revokeServerTokens = any;
|
export type revokeServerTokens = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type isChannelAllowlisted = any;
|
export type isChannelAllowlisted = any
|
||||||
export type isChannelsEnabled = any;
|
export type isChannelsEnabled = any
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type ChannelMessageNotificationSchema = any;
|
export type ChannelMessageNotificationSchema = any
|
||||||
export type gateChannelServer = any;
|
export type gateChannelServer = any
|
||||||
export type wrapChannelMessage = any;
|
export type wrapChannelMessage = any
|
||||||
export type findChannelEntry = any;
|
export type findChannelEntry = any
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type setupSdkMcpClients = any;
|
export type setupSdkMcpClients = any
|
||||||
export type connectToServer = any;
|
export type connectToServer = any
|
||||||
export type clearServerCache = any;
|
export type clearServerCache = any
|
||||||
export type fetchToolsForClient = any;
|
export type fetchToolsForClient = any
|
||||||
export type areMcpConfigsEqual = any;
|
export type areMcpConfigsEqual = any
|
||||||
export type reconnectMcpServerImpl = any;
|
export type reconnectMcpServerImpl = any
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type filterMcpServersByPolicy = any;
|
export type filterMcpServersByPolicy = any
|
||||||
export type getMcpConfigByName = any;
|
export type getMcpConfigByName = any
|
||||||
export type isMcpServerDisabled = any;
|
export type isMcpServerDisabled = any
|
||||||
export type setMcpServerEnabled = any;
|
export type setMcpServerEnabled = any
|
||||||
export type getAllMcpConfigs = any;
|
export type getAllMcpConfigs = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type runElicitationHooks = any;
|
export type runElicitationHooks = any
|
||||||
export type runElicitationResultHooks = any;
|
export type runElicitationResultHooks = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type getMcpPrefix = any;
|
export type getMcpPrefix = any
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type MCPServerConnection = any;
|
export type MCPServerConnection = any
|
||||||
export type McpSdkServerConfig = any;
|
export type McpSdkServerConfig = any
|
||||||
export type ScopedMcpServerConfig = any;
|
export type ScopedMcpServerConfig = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type commandBelongsToServer = any;
|
export type commandBelongsToServer = any
|
||||||
export type filterToolsByServer = any;
|
export type filterToolsByServer = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type setupVscodeSdkMcp = any;
|
export type setupVscodeSdkMcp = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type OAuthService = any;
|
export type OAuthService = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type isPolicyAllowed = any;
|
export type isPolicyAllowed = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type waitForRemoteManagedSettingsToLoad = any;
|
export type waitForRemoteManagedSettingsToLoad = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type downloadUserSettings = any;
|
export type downloadUserSettings = any
|
||||||
export type redownloadUserSettings = any;
|
export type redownloadUserSettings = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type AppState = any;
|
export type AppState = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type externalMetadataToAppState = any;
|
export type externalMetadataToAppState = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type assembleToolPool = any;
|
export type assembleToolPool = any
|
||||||
export type filterToolsByDenyRules = any;
|
export type filterToolsByDenyRules = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type createAbortController = any;
|
export type createAbortController = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type uniq = any;
|
export type uniq = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type getAccountInformation = any;
|
export type getAccountInformation = any
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type getLatestVersion = any;
|
export type getLatestVersion = any
|
||||||
export type InstallStatus = any;
|
export type InstallStatus = any
|
||||||
export type installGlobalPackage = any;
|
export type installGlobalPackage = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type AwsAuthStatusManager = any;
|
export type AwsAuthStatusManager = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type modelSupportsAutoMode = any;
|
export type modelSupportsAutoMode = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type registerCleanup = any;
|
export type registerCleanup = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type createCombinedAbortSignal = any;
|
export type createCombinedAbortSignal = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type notifyCommandLifecycle = any;
|
export type notifyCommandLifecycle = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type incrementPromptCount = any;
|
export type incrementPromptCount = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type regenerateCompletionCache = any;
|
export type regenerateCompletionCache = any
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type getGlobalConfig = any;
|
export type getGlobalConfig = any
|
||||||
export type InstallMethod = any;
|
export type InstallMethod = any
|
||||||
export type saveGlobalConfig = any;
|
export type saveGlobalConfig = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type loadConversationForResume = any;
|
export type loadConversationForResume = any
|
||||||
export type TurnInterruptionState = any;
|
export type TurnInterruptionState = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type getCwd = any;
|
export type getCwd = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type logForDebugging = any;
|
export type logForDebugging = any
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type logForDiagnosticsNoPII = any;
|
export type logForDiagnosticsNoPII = any
|
||||||
export type withDiagnosticsTiming = any;
|
export type withDiagnosticsTiming = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type getDoctorDiagnostic = any;
|
export type getDoctorDiagnostic = any
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type modelSupportsEffort = any;
|
export type modelSupportsEffort = any
|
||||||
export type modelSupportsMaxEffort = any;
|
export type modelSupportsMaxEffort = any
|
||||||
export type EFFORT_LEVELS = any;
|
export type EFFORT_LEVELS = any
|
||||||
export type resolveAppliedEffort = any;
|
export type resolveAppliedEffort = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type AbortError = any;
|
export type AbortError = any
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type isFastModeAvailable = any;
|
export type isFastModeAvailable = any
|
||||||
export type isFastModeEnabled = any;
|
export type isFastModeEnabled = any
|
||||||
export type isFastModeSupportedByModel = any;
|
export type isFastModeSupportedByModel = any
|
||||||
export type getFastModeState = any;
|
export type getFastModeState = any
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type fileHistoryRewind = any;
|
export type fileHistoryRewind = any
|
||||||
export type fileHistoryCanRestore = any;
|
export type fileHistoryCanRestore = any
|
||||||
export type fileHistoryEnabled = any;
|
export type fileHistoryEnabled = any
|
||||||
export type fileHistoryGetDiffStats = any;
|
export type fileHistoryGetDiffStats = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type executeFilePersistence = any;
|
export type executeFilePersistence = any
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type createFileStateCacheWithSizeLimit = any;
|
export type createFileStateCacheWithSizeLimit = any
|
||||||
export type mergeFileStateCaches = any;
|
export type mergeFileStateCaches = any
|
||||||
export type READ_FILE_STATE_CACHE_SIZE = any;
|
export type READ_FILE_STATE_CACHE_SIZE = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type getLastCacheSafeParams = any;
|
export type getLastCacheSafeParams = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type fromArray = any;
|
export type fromArray = any
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type gracefulShutdown = any;
|
export type gracefulShutdown = any
|
||||||
export type gracefulShutdownSync = any;
|
export type gracefulShutdownSync = any
|
||||||
export type isShuttingDown = any;
|
export type isShuttingDown = any
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type headlessProfilerStartTurn = any;
|
export type headlessProfilerStartTurn = any
|
||||||
export type headlessProfilerCheckpoint = any;
|
export type headlessProfilerCheckpoint = any
|
||||||
export type logHeadlessProfilerTurn = any;
|
export type logHeadlessProfilerTurn = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type executeNotificationHooks = any;
|
export type executeNotificationHooks = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type finalizePendingAsyncHooks = any;
|
export type finalizePendingAsyncHooks = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type registerHookEventHandler = any;
|
export type registerHookEventHandler = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type createIdleTimeoutManager = any;
|
export type createIdleTimeoutManager = any
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Auto-generated type stub — replace with real implementation
|
// Auto-generated type stub — replace with real implementation
|
||||||
export type safeParseJSON = any;
|
export type safeParseJSON = any
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user