From 21865576a723af2f963805aec894f6cf3cbc52f2 Mon Sep 17 00:00:00 2001 From: jariy17 Date: Wed, 17 Jun 2026 22:57:51 +0000 Subject: [PATCH] chore: remove dead code and unused exports (no behavior change) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Whole-repo over-engineering audit. Removes verified-dead code only — no CLI command, flag, help text, output, or public API surface changes. All cuts confirmed zero-caller across src + tests + e2e/integ/browser suites before removal. Deletions (whole files, zero importers): - tui/hooks/useAttach.ts (12 unused hooks) - tui/hooks/usePanelNavigation.ts (+ test) - tui/hooks/use-gradient.ts ("kept for future use") - tui/components/ScrollableText.tsx, TwoColumn.tsx (+ tests, + barrel lines) - lib/packaging/types/archiver.d.ts (archiver never imported; uses fflate) - schema/schemas/primitives/index.ts (unreachable re-export barrel) - cli/cloudformation/types.ts (4 unused exports, + barrel line) - cli/commands/index.ts (stale unused register* barrel) Dead exports / members removed: - operations/session: getSessionId/saveSessionId/clearSessionId/getOrCreateSessionId - aws/agentcore: stopRuntimeSession (+ types, + barrel + SDK import) - primitives: PolicyEnginePrimitive.getDeployedGatewayArn, RuntimeEndpoint stub - lib/constants: HARNESS_DIR, NPM_INSTALL_HINT - lib/utils/platform: isLinux, getShellCommand, getShellArgs, normalizeCommand (+ test) - lib/packaging/helpers: runCommand wrapper - schema harness: HarnessSkillInput - cdk wrapper: getProjectDir - tui-harness: session-manager get/listAll, screen formatNumbered, availability.unavailableReason - dead barrel re-export lines in templates/, external-requirements/, primitives/, commands/add/types - dropped unused `export` keyword on internal-only helpers (otel transforms, span-collector executeQuery, feedback build-payload, ab-test resolve) Stdlib: replace two hand-rolled sleep() helpers with node:timers/promises setTimeout (dataset/push, insights/run-insights). Verified: tsc --noEmit (src+tests), eslint (0 errors), npm run build, npm test (5345 passed). --- src/cli/aws/agentcore.ts | 34 -- src/cli/aws/index.ts | 3 - src/cli/cdk/toolkit-lib/wrapper.ts | 7 - src/cli/cloudformation/index.ts | 1 - src/cli/cloudformation/types.ts | 30 -- src/cli/commands/add/types.ts | 8 - src/cli/commands/import/constants.ts | 13 - src/cli/commands/index.ts | 19 - src/cli/external-requirements/index.ts | 4 - src/cli/operations/dataset/push.ts | 5 +- src/cli/operations/dev/otel/transforms.ts | 10 +- .../operations/eval/shared/span-collector.ts | 2 +- src/cli/operations/feedback/build-payload.ts | 4 +- src/cli/operations/insights/run-insights.ts | 5 +- src/cli/operations/jobs/ab-test/resolve.ts | 4 +- src/cli/operations/session/index.ts | 127 ------- src/cli/primitives/PolicyEnginePrimitive.ts | 6 - .../primitives/RuntimeEndpointPrimitive.ts | 5 - src/cli/primitives/index.ts | 33 +- src/cli/primitives/types.ts | 2 - src/cli/templates/index.ts | 5 - src/cli/tui/components/ScrollableText.tsx | 266 -------------- src/cli/tui/components/TwoColumn.tsx | 36 -- .../__tests__/ScrollableText.test.tsx | 150 -------- .../components/__tests__/TwoColumn.test.tsx | 94 ----- src/cli/tui/components/index.ts | 4 +- .../__tests__/usePanelNavigation.test.tsx | 347 ------------------ src/cli/tui/hooks/use-gradient.ts | 38 -- src/cli/tui/hooks/useAttach.ts | 159 -------- src/cli/tui/hooks/usePanelNavigation.ts | 196 ---------- src/lib/constants.ts | 4 - src/lib/packaging/helpers.ts | 6 +- src/lib/packaging/types/archiver.d.ts | 42 --- src/lib/utils/__tests__/platform.test.ts | 86 +---- src/lib/utils/platform.ts | 46 --- src/schema/schemas/primitives/harness.ts | 1 - src/schema/schemas/primitives/index.ts | 121 ------ src/tui-harness/index.ts | 2 +- src/tui-harness/lib/availability.ts | 13 +- src/tui-harness/lib/screen.ts | 22 -- src/tui-harness/lib/session-manager.ts | 19 - 41 files changed, 21 insertions(+), 1958 deletions(-) delete mode 100644 src/cli/cloudformation/types.ts delete mode 100644 src/cli/commands/index.ts delete mode 100644 src/cli/tui/components/ScrollableText.tsx delete mode 100644 src/cli/tui/components/TwoColumn.tsx delete mode 100644 src/cli/tui/components/__tests__/ScrollableText.test.tsx delete mode 100644 src/cli/tui/components/__tests__/TwoColumn.test.tsx delete mode 100644 src/cli/tui/hooks/__tests__/usePanelNavigation.test.tsx delete mode 100644 src/cli/tui/hooks/use-gradient.ts delete mode 100644 src/cli/tui/hooks/useAttach.ts delete mode 100644 src/cli/tui/hooks/usePanelNavigation.ts delete mode 100644 src/lib/packaging/types/archiver.d.ts delete mode 100644 src/schema/schemas/primitives/index.ts diff --git a/src/cli/aws/agentcore.ts b/src/cli/aws/agentcore.ts index b91df356d..1f389d9b0 100644 --- a/src/cli/aws/agentcore.ts +++ b/src/cli/aws/agentcore.ts @@ -8,7 +8,6 @@ import { EvaluateCommand, InvokeAgentRuntimeCommand, InvokeAgentRuntimeCommandCommand, - StopRuntimeSessionCommand, } from '@aws-sdk/client-bedrock-agentcore'; import type { HttpRequest } from '@smithy/protocol-http'; import type { DocumentType } from '@smithy/types'; @@ -96,17 +95,6 @@ export interface StreamingInvokeResult { sessionId: string | undefined; } -export interface StopRuntimeSessionOptions { - region: string; - runtimeArn: string; - sessionId: string; -} - -export interface StopRuntimeSessionResult { - sessionId: string | undefined; - statusCode: number | undefined; -} - /** * Parse a single SSE data line and extract the content. * Returns null if the line is not a data line or contains an error. @@ -1118,28 +1106,6 @@ export async function invokeAguiRuntime( }; } -/** - * Stop a runtime session. - */ -export async function stopRuntimeSession(options: StopRuntimeSessionOptions): Promise { - const client = new BedrockAgentCoreClient({ - region: options.region, - credentials: getCredentialProvider(), - }); - - const command = new StopRuntimeSessionCommand({ - agentRuntimeArn: options.runtimeArn, - runtimeSessionId: options.sessionId, - }); - - const response = await client.send(command); - - return { - sessionId: response.runtimeSessionId, - statusCode: response.statusCode, - }; -} - // --------------------------------------------------------------------------- // Execute Bash: Run shell commands in runtime containers // --------------------------------------------------------------------------- diff --git a/src/cli/aws/index.ts b/src/cli/aws/index.ts index 09851e678..b63d4206f 100644 --- a/src/cli/aws/index.ts +++ b/src/cli/aws/index.ts @@ -62,7 +62,6 @@ export { mcpCallTool, parseSSE, extractResult, - stopRuntimeSession, type ExecuteBashOptions, type ExecuteBashResult, type ExecuteBashStreamEvent, @@ -72,8 +71,6 @@ export { type McpToolDef, type McpListToolsResult, type StreamingInvokeResult, - type StopRuntimeSessionOptions, - type StopRuntimeSessionResult, } from './agentcore'; export { startRecommendation, diff --git a/src/cli/cdk/toolkit-lib/wrapper.ts b/src/cli/cdk/toolkit-lib/wrapper.ts index 0ba5a474e..c3dacf5d8 100644 --- a/src/cli/cdk/toolkit-lib/wrapper.ts +++ b/src/cli/cdk/toolkit-lib/wrapper.ts @@ -74,13 +74,6 @@ export class CdkToolkitWrapper { this.options = options; } - /** - * Get the path to the CDK project directory. - */ - getProjectDir(): string { - return this.projectDir; - } - /** * Get the CDK app command for this project. * Points to the compiled JS file in dist/ (requires tsc build first). diff --git a/src/cli/cloudformation/index.ts b/src/cli/cloudformation/index.ts index 4c41c05c8..100c195d6 100644 --- a/src/cli/cloudformation/index.ts +++ b/src/cli/cloudformation/index.ts @@ -2,4 +2,3 @@ export * from './bootstrap'; export * from './outputs'; export * from './stack-discovery'; export * from './stack-status'; -export * from './types'; diff --git a/src/cli/cloudformation/types.ts b/src/cli/cloudformation/types.ts deleted file mode 100644 index 3c3e3bf9d..000000000 --- a/src/cli/cloudformation/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { StackEvent } from '@aws-sdk/client-cloudformation'; - -export const TERMINAL_STACK_STATUSES = new Set([ - 'CREATE_COMPLETE', - 'UPDATE_COMPLETE', - 'UPDATE_ROLLBACK_COMPLETE', - 'ROLLBACK_COMPLETE', - 'CREATE_FAILED', - 'DELETE_FAILED', - 'DELETE_COMPLETE', - 'UPDATE_ROLLBACK_FAILED', - 'ROLLBACK_FAILED', - 'IMPORT_COMPLETE', - 'IMPORT_ROLLBACK_COMPLETE', - 'IMPORT_ROLLBACK_FAILED', -]); - -export const FAILURE_LIKE = (s?: string) => - !!s && (s.endsWith('_FAILED') || s.includes('ROLLBACK') || s === 'DELETE_FAILED'); - -export function getStatusColor(status: string): 'green' | 'red' | 'yellow' | 'white' { - if (status.includes('COMPLETE') && !status.includes('ROLLBACK')) return 'green'; - if (status.includes('FAILED') || status.includes('ROLLBACK')) return 'red'; - if (status.includes('PROGRESS') || status.includes('IN_PROGRESS')) return 'yellow'; - return 'white'; -} - -export function isFailureEvent(ev: StackEvent) { - return (ev.ResourceStatus ?? '').endsWith('FAILED'); -} diff --git a/src/cli/commands/add/types.ts b/src/cli/commands/add/types.ts index 465c4d717..22e8919bd 100644 --- a/src/cli/commands/add/types.ts +++ b/src/cli/commands/add/types.ts @@ -195,11 +195,6 @@ export interface AddDatasetOptions { json?: boolean; } -export interface AddDatasetResult { - success: boolean; - datasetName?: string; - error?: string; -} // Credential types (v2: credential, no owner/user concept) export interface AddCredentialOptions { name?: string; @@ -211,6 +206,3 @@ export interface AddCredentialOptions { scopes?: string; json?: boolean; } - -/** @deprecated Use AddCredentialOptions */ -export type AddIdentityOptions = AddCredentialOptions; diff --git a/src/cli/commands/import/constants.ts b/src/cli/commands/import/constants.ts index 6219d278c..141524f7a 100644 --- a/src/cli/commands/import/constants.ts +++ b/src/cli/commands/import/constants.ts @@ -1,19 +1,6 @@ /** Name validation regex used by all import handlers. */ export const NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/; -/** - * CloudFormation resource type to identifier key mapping for IMPORT. - */ -export const CFN_RESOURCE_IDENTIFIERS: Record = { - 'AWS::BedrockAgentCore::Runtime': ['AgentRuntimeId'], - 'AWS::BedrockAgentCore::Memory': ['MemoryId'], - 'AWS::BedrockAgentCore::Gateway': ['GatewayIdentifier'], - 'AWS::BedrockAgentCore::GatewayTarget': ['GatewayIdentifier', 'TargetId'], - 'AWS::BedrockAgentCore::Evaluator': ['EvaluatorId'], - 'AWS::BedrockAgentCore::OnlineEvaluationConfig': ['OnlineEvaluationConfigId'], - 'AWS::BedrockAgentCore::Harness': ['HarnessId'], -}; - /** * CloudFormation resource types that are primary (importable) resources. * Everything else is a companion resource. diff --git a/src/cli/commands/index.ts b/src/cli/commands/index.ts deleted file mode 100644 index 94188b411..000000000 --- a/src/cli/commands/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Command registrations -export { registerArchive } from './archive'; -export { registerAdd } from './add'; -export { registerDeploy } from './deploy'; -export { registerDev } from './dev'; -export { registerCreate } from './create'; -export { registerEval } from './eval'; -export { registerExec } from './exec'; -export { registerFetch } from './fetch'; -export { registerInvoke } from './invoke'; -export { registerPackage } from './package'; -export { registerPause } from './pause'; -export { registerRemove } from './remove'; -export { registerResume } from './resume'; -export { registerRun } from './run'; -export { registerStop } from './stop'; -export { registerStatus } from './status'; -export { registerTraces } from './traces'; -export { registerUpdate } from './update'; diff --git a/src/cli/external-requirements/index.ts b/src/cli/external-requirements/index.ts index 3ebce3e1a..e5e0e573f 100644 --- a/src/cli/external-requirements/index.ts +++ b/src/cli/external-requirements/index.ts @@ -11,13 +11,9 @@ export { export { checkNodeVersion, checkUvVersion, - checkAwsCliVersion, - checkNpmCacheOwnership, getAwsLoginGuidance, formatVersionError, - formatNpmCacheError, requiresUv, - requiresContainerRuntime, checkDependencyVersions, checkCreateDependencies, type VersionCheckResult, diff --git a/src/cli/operations/dataset/push.ts b/src/cli/operations/dataset/push.ts index 6999f90b0..0c824124f 100644 --- a/src/cli/operations/dataset/push.ts +++ b/src/cli/operations/dataset/push.ts @@ -21,6 +21,7 @@ import stableStringify from 'fast-json-stable-stringify'; import { randomUUID } from 'node:crypto'; import { readFile, writeFile } from 'node:fs/promises'; import { resolve } from 'node:path'; +import { setTimeout as sleep } from 'node:timers/promises'; /** Maximum examples per API call (service limit). */ const API_BATCH_LIMIT = 1000; @@ -179,10 +180,6 @@ async function batchOperation(options: { return results; } -function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} - /** * Write exampleIds back into the local JSONL file after push. * In force mode (no addedExamples), all examples get new IDs. diff --git a/src/cli/operations/dev/otel/transforms.ts b/src/cli/operations/dev/otel/transforms.ts index 72f42d7e1..7f429f09c 100644 --- a/src/cli/operations/dev/otel/transforms.ts +++ b/src/cli/operations/dev/otel/transforms.ts @@ -4,7 +4,7 @@ import type { OtlpAttribute, OtlpAttributeValue, OtlpResource, OtlpResourceLog, // Trace metadata extraction (from raw OTLP data) // --------------------------------------------------------------------------- -export interface TraceMeta { +interface TraceMeta { traceId?: string; firstSeen: number; lastSeen: number; @@ -197,7 +197,7 @@ function normalizeSpanKind(kind: number | string | undefined): number { // --------------------------------------------------------------------------- /** Convert nanosecond timestamp (string) to milliseconds. */ -export function nanoToMs(nano: string | undefined): number { +function nanoToMs(nano: string | undefined): number { if (!nano) return 0; return Math.floor(Number(nano) / 1_000_000); } @@ -206,7 +206,7 @@ export function nanoToMs(nano: string | undefined): number { * Convert a value that may be base64 (from protobuf JSON roundtrip) or * already a hex string into a hex string. */ -export function hexFromB64OrString(val: string | undefined): string { +function hexFromB64OrString(val: string | undefined): string { if (!val) return ''; // Already hex (32 chars for traceId, 16 for spanId) if (/^[0-9a-f]+$/i.test(val) && (val.length === 32 || val.length === 16)) return val.toLowerCase(); @@ -239,7 +239,7 @@ function getAttrValue(attrs: OtlpAttribute[] | Record | undefin * Flatten OTLP attributes to a plain Record. * Handles both OTLP key/value array format and already-flat records. */ -export function flattenAttributes( +function flattenAttributes( attrs: OtlpAttribute[] | Record | undefined ): Record | undefined { if (!attrs) return undefined; @@ -263,7 +263,7 @@ export function flattenAttributes( } /** Extract a usable value from an OTLP AnyValue. */ -export function extractAnyValue(val: unknown): unknown { +function extractAnyValue(val: unknown): unknown { if (!val || typeof val !== 'object') return val; const v = val as Record; if (v.stringValue !== undefined) return v.stringValue; diff --git a/src/cli/operations/eval/shared/span-collector.ts b/src/cli/operations/eval/shared/span-collector.ts index deafb709c..0d35ed091 100644 --- a/src/cli/operations/eval/shared/span-collector.ts +++ b/src/cli/operations/eval/shared/span-collector.ts @@ -178,7 +178,7 @@ export async function executeQueryGraceful( /** * Execute a CloudWatch Logs Insights query and wait for results. */ -export async function executeQuery( +async function executeQuery( client: CloudWatchLogsClient, logGroupName: string, queryString: string, diff --git a/src/cli/operations/feedback/build-payload.ts b/src/cli/operations/feedback/build-payload.ts index cb10f6d61..5fe4df155 100644 --- a/src/cli/operations/feedback/build-payload.ts +++ b/src/cli/operations/feedback/build-payload.ts @@ -21,11 +21,11 @@ interface BuildPayloadInput { osDescriptor?: string; } -export function buildOsDescriptor(): string { +function buildOsDescriptor(): string { return `${process.platform} ${os.release()}`; } -export function buildLocationDescriptor(cliVersion: string, mode: FeedbackMode): string { +function buildLocationDescriptor(cliVersion: string, mode: FeedbackMode): string { return `agentcore-cli@${cliVersion} (${process.platform}; node ${process.version}; ${mode})`; } diff --git a/src/cli/operations/insights/run-insights.ts b/src/cli/operations/insights/run-insights.ts index 4b443e0d2..9d0580c23 100644 --- a/src/cli/operations/insights/run-insights.ts +++ b/src/cli/operations/insights/run-insights.ts @@ -15,6 +15,7 @@ import { getRegion } from '../../commands/shared/region-utils'; import { ExecLogger } from '../../logging/exec-logger'; import { saveInsightsRun, updateInsightsRun } from './insights-storage'; import type { InsightsRunRecord, RunInsightsOptions, RunInsightsResult } from './types'; +import { setTimeout as sleep } from 'node:timers/promises'; // ============================================================================ // Constants @@ -225,7 +226,3 @@ function buildFilterConfig(options: RunInsightsOptions): CloudWatchFilterConfig const startTime = options.startTime ?? new Date(Date.now() - lookbackDays * 24 * 60 * 60 * 1000).toISOString(); return { timeRange: { startTime, endTime } }; } - -function sleep(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/src/cli/operations/jobs/ab-test/resolve.ts b/src/cli/operations/jobs/ab-test/resolve.ts index 2aafdc6b0..813757de6 100644 --- a/src/cli/operations/jobs/ab-test/resolve.ts +++ b/src/cli/operations/jobs/ab-test/resolve.ts @@ -29,7 +29,7 @@ export const IAM_PROPAGATION_DELAY_MS = 15_000; // ============================================================================ /** Generate a project-scoped role name: AgentCore-{ProjectName}-ABTest{TestName}-{Hash} (max 64 chars). */ -export function generateRoleName(projectName: string, testName: string): string { +function generateRoleName(projectName: string, testName: string): string { // Deterministic hash so retries produce the same role name (avoids orphaned roles). const hash = createHash('sha256').update(`${projectName}:${testName}`).digest('hex').slice(0, 8); const base = `AgentCore-${projectName}-ABTest${testName}`; @@ -37,7 +37,7 @@ export function generateRoleName(projectName: string, testName: string): string } /** Extract role name from ARN: arn:aws:iam::123456789012:role/RoleName → RoleName. */ -export function roleNameFromArn(roleArn: string): string { +function roleNameFromArn(roleArn: string): string { const parts = roleArn.split('/'); return parts[parts.length - 1] ?? roleArn; } diff --git a/src/cli/operations/session/index.ts b/src/cli/operations/session/index.ts index 14bba70c1..9d8acce9b 100644 --- a/src/cli/operations/session/index.ts +++ b/src/cli/operations/session/index.ts @@ -1,4 +1,3 @@ -import { ConfigIO } from '../../../lib'; import { randomUUID } from 'crypto'; /** @@ -7,129 +6,3 @@ import { randomUUID } from 'crypto'; export function generateSessionId(): string { return randomUUID(); } - -export interface SessionInfo { - sessionId: string | undefined; - agentName: string; - runtimeArn: string; - targetName: string; -} - -/** - * Get the session ID for an agent from the deployed state. - */ -export async function getSessionId( - agentName: string, - targetName?: string, - configIO: ConfigIO = new ConfigIO() -): Promise { - const deployedState = await configIO.readDeployedState(); - const awsTargets = await configIO.readAWSDeploymentTargets(); - - // Resolve target - const targetNames = Object.keys(deployedState.targets); - if (targetNames.length === 0) { - return null; - } - - const selectedTargetName = targetName ?? targetNames[0]!; - const targetState = deployedState.targets[selectedTargetName]; - const targetConfig = awsTargets.find(t => t.name === selectedTargetName); - - if (!targetConfig || !targetState?.resources?.runtimes) { - return null; - } - - const agentState = targetState.resources.runtimes[agentName]; - if (!agentState) { - return null; - } - - return { - sessionId: agentState.sessionId, - agentName, - runtimeArn: agentState.runtimeArn, - targetName: selectedTargetName, - }; -} - -/** - * Save a session ID for an agent to the deployed state. - */ -export async function saveSessionId( - agentName: string, - sessionId: string, - targetName?: string, - configIO: ConfigIO = new ConfigIO() -): Promise { - const deployedState = await configIO.readDeployedState(); - - // Resolve target - const targetNames = Object.keys(deployedState.targets); - if (targetNames.length === 0) { - throw new Error('No deployed targets found'); - } - - const selectedTargetName = targetName ?? targetNames[0]!; - const targetState = deployedState.targets[selectedTargetName]; - - if (!targetState?.resources?.runtimes?.[agentName]) { - throw new Error(`Agent '${agentName}' not found in deployed state`); - } - - // Update the session ID - targetState.resources.runtimes[agentName].sessionId = sessionId; - - await configIO.writeDeployedState(deployedState); -} - -/** - * Clear the session ID for an agent from the deployed state. - */ -export async function clearSessionId( - agentName: string, - targetName?: string, - configIO: ConfigIO = new ConfigIO() -): Promise { - const deployedState = await configIO.readDeployedState(); - - // Resolve target - const targetNames = Object.keys(deployedState.targets); - if (targetNames.length === 0) { - return; - } - - const selectedTargetName = targetName ?? targetNames[0]!; - const targetState = deployedState.targets[selectedTargetName]; - - if (!targetState?.resources?.runtimes?.[agentName]) { - return; - } - - // Clear the session ID - delete targetState.resources.runtimes[agentName].sessionId; - - await configIO.writeDeployedState(deployedState); -} - -/** - * Get or create a session ID for an agent. - * If a session ID exists in the deployed state, returns it. - * Otherwise, generates a new one and saves it. - */ -export async function getOrCreateSessionId( - agentName: string, - targetName?: string, - configIO: ConfigIO = new ConfigIO() -): Promise { - const sessionInfo = await getSessionId(agentName, targetName, configIO); - - if (sessionInfo?.sessionId) { - return sessionInfo.sessionId; - } - - const newSessionId = generateSessionId(); - await saveSessionId(agentName, newSessionId, targetName, configIO); - - return newSessionId; -} diff --git a/src/cli/primitives/PolicyEnginePrimitive.ts b/src/cli/primitives/PolicyEnginePrimitive.ts index bb4cdf306..225f85f11 100644 --- a/src/cli/primitives/PolicyEnginePrimitive.ts +++ b/src/cli/primitives/PolicyEnginePrimitive.ts @@ -178,12 +178,6 @@ export class PolicyEnginePrimitive extends BasePrimitive { - const gateways = await this.getDeployedGateways(); - const firstArn = Object.values(gateways)[0]; - return firstArn ?? null; - } - /** * Get deployed gateways, excluding MCP protocol gateways. * Guardrails policies only apply to HTTP (protocolType: "None") gateways. diff --git a/src/cli/primitives/RuntimeEndpointPrimitive.ts b/src/cli/primitives/RuntimeEndpointPrimitive.ts index 725ee8139..86ac78028 100644 --- a/src/cli/primitives/RuntimeEndpointPrimitive.ts +++ b/src/cli/primitives/RuntimeEndpointPrimitive.ts @@ -354,9 +354,4 @@ export class RuntimeEndpointPrimitive extends BasePrimitive directly */ export type AddResult = Record> = Result; diff --git a/src/cli/templates/index.ts b/src/cli/templates/index.ts index e41e563b3..cb17fccbf 100644 --- a/src/cli/templates/index.ts +++ b/src/cli/templates/index.ts @@ -10,12 +10,7 @@ import type { AgentRenderConfig } from './types'; export { BaseRenderer, type RendererContext } from './BaseRenderer'; export { CDKRenderer, type CDKRendererContext } from './CDKRenderer'; export { renderGatewayTargetTemplate } from './GatewayTargetRenderer'; -export { GoogleADKRenderer } from './GoogleADKRenderer'; -export { LangGraphRenderer } from './LangGraphRenderer'; -export { McpRenderer } from './McpRenderer'; -export { OpenAIAgentsRenderer } from './OpenAIAgentsRenderer'; export { StrandsRenderer } from './StrandsRenderer'; -export { VercelAIRenderer } from './VercelAIRenderer'; export type { AgentRenderConfig } from './types'; /** diff --git a/src/cli/tui/components/ScrollableText.tsx b/src/cli/tui/components/ScrollableText.tsx deleted file mode 100644 index 1f02968a4..000000000 --- a/src/cli/tui/components/ScrollableText.tsx +++ /dev/null @@ -1,266 +0,0 @@ -import { Box, Text, useInput, useStdout } from 'ink'; -import React, { useCallback, useMemo, useState } from 'react'; - -// Scrollbar characters -const SCROLLBAR_THUMB = '█'; -const SCROLLBAR_TRACK = '░'; - -interface ScrollableTextProps { - /** Text content to display (will be split by newlines) */ - content: string; - /** Color for the text */ - color?: string; - /** Fixed height in lines (defaults to auto-calculated from terminal) */ - height?: number; - /** Whether content is currently streaming (enables auto-scroll) */ - isStreaming?: boolean; - /** Whether this component should handle input (default true) */ - isActive?: boolean; - /** Minimum height in lines */ - minHeight?: number; - /** Maximum height in lines */ - maxHeight?: number; - /** Whether to show the scrollbar (default true when scrolling needed) */ - showScrollbar?: boolean; -} - -/** - * Word-wrap a single line to fit within maxWidth. - * Returns array of wrapped line segments. - */ -function wrapLine(line: string, maxWidth: number): string[] { - if (!line) return ['']; - if (line.length <= maxWidth) return [line]; - - const wrapped: string[] = []; - const words = line.split(' '); - let currentLine = ''; - - for (const word of words) { - // If word itself is longer than maxWidth, break it - if (word.length > maxWidth) { - if (currentLine) { - wrapped.push(currentLine); - currentLine = ''; - } - // Break long word into chunks - for (let i = 0; i < word.length; i += maxWidth) { - wrapped.push(word.slice(i, i + maxWidth)); - } - continue; - } - - const testLine = currentLine ? `${currentLine} ${word}` : word; - if (testLine.length <= maxWidth) { - currentLine = testLine; - } else { - if (currentLine) { - wrapped.push(currentLine); - } - currentLine = word; - } - } - - if (currentLine) { - wrapped.push(currentLine); - } - - return wrapped.length > 0 ? wrapped : ['']; -} - -/** - * Wrap multi-line text to fit within maxWidth. - * Preserves original line breaks and adds new ones for long lines. - */ -function wrapText(text: string, maxWidth: number): string[] { - if (!text) return []; - - const lines = text.split('\n'); - const wrapped: string[] = []; - - for (const line of lines) { - wrapped.push(...wrapLine(line, maxWidth)); - } - - return wrapped; -} - -/** - * Calculate scrollbar thumb position and size. - */ -function calculateScrollbar( - totalLines: number, - displayHeight: number, - scrollOffset: number -): { thumbStart: number; thumbSize: number } { - if (totalLines <= displayHeight) { - return { thumbStart: 0, thumbSize: displayHeight }; - } - - // Thumb size proportional to visible content - const thumbSize = Math.max(1, Math.round((displayHeight / totalLines) * displayHeight)); - - // Thumb position proportional to scroll position - const maxScroll = totalLines - displayHeight; - const scrollRatio = maxScroll > 0 ? scrollOffset / maxScroll : 0; - const thumbStart = Math.round(scrollRatio * (displayHeight - thumbSize)); - - return { thumbStart, thumbSize }; -} - -/** - * Scrollable text display for multi-line content. - * Auto-scrolls to bottom during streaming, allows manual scroll with ↑↓ and mouse wheel. - * Handles text wrapping based on terminal width. - * Displays a visual scrollbar when content overflows. - */ -export function ScrollableText({ - content, - color, - height: fixedHeight, - isStreaming = false, - isActive = true, - minHeight = 5, - maxHeight = 20, - showScrollbar = true, -}: ScrollableTextProps) { - const { stdout } = useStdout(); - const [scrollOffset, setScrollOffset] = useState(0); - const [userScrolled, setUserScrolled] = useState(false); - - // Get terminal width (with buffer for margins and scrollbar) - const terminalWidth = useMemo(() => { - const width = stdout?.columns ?? 80; - // Leave margin for borders and scrollbar (3 chars for scrollbar area) - return Math.max(40, width - 6); - }, [stdout?.columns]); - - // Wrap content into display lines based on terminal width - const lines = useMemo(() => { - return wrapText(content, terminalWidth); - }, [content, terminalWidth]); - - // Calculate display height - const displayHeight = useMemo(() => { - if (fixedHeight) return fixedHeight; - // Use terminal height minus buffer for header/footer - const terminalHeight = stdout?.rows ?? 24; - const availableHeight = Math.max(minHeight, terminalHeight - 12); - return Math.min(maxHeight, availableHeight); - }, [fixedHeight, stdout?.rows, minHeight, maxHeight]); - - const totalLines = lines.length; - const maxScroll = Math.max(0, totalLines - displayHeight); - const needsScroll = totalLines > displayHeight; - - // Determine effective scroll position: - // - When streaming and user hasn't scrolled: show bottom (maxScroll) - // - When user has scrolled: show their position (clamped to valid range) - // - When content is empty: show top (0) - const effectiveOffset = useMemo(() => { - if (totalLines === 0) return 0; - if (isStreaming && !userScrolled) return maxScroll; - // Clamp scroll offset to valid range (content may have shrunk) - return Math.min(scrollOffset, maxScroll); - }, [totalLines, isStreaming, userScrolled, scrollOffset, maxScroll]); - - // Calculate scrollbar position - const { thumbStart, thumbSize } = useMemo( - () => calculateScrollbar(totalLines, displayHeight, effectiveOffset), - [totalLines, displayHeight, effectiveOffset] - ); - - // Scroll handler for both keyboard and mouse - const scrollUp = useCallback( - (amount = 1) => { - if (!needsScroll) return; - setUserScrolled(true); - setScrollOffset(prev => Math.max(0, prev - amount)); - }, - [needsScroll] - ); - - const scrollDown = useCallback( - (amount = 1) => { - if (!needsScroll) return; - setScrollOffset(prev => { - const next = Math.min(maxScroll, prev + amount); - if (next >= maxScroll) { - setUserScrolled(false); - } - return next; - }); - }, - [needsScroll, maxScroll] - ); - - // Handle keyboard scroll input - useInput( - (_input, key) => { - if (!needsScroll) return; - - if (key.upArrow) { - scrollUp(1); - } else if (key.downArrow) { - scrollDown(1); - } else if (key.pageUp) { - scrollUp(displayHeight); - } else if (key.pageDown) { - scrollDown(displayHeight); - } - }, - { isActive: isActive && needsScroll } - ); - - // Get visible lines based on effective offset - const visibleLines = useMemo(() => { - return lines.slice(effectiveOffset, effectiveOffset + displayHeight); - }, [lines, effectiveOffset, displayHeight]); - - // Generate scrollbar for each line - const renderScrollbar = useCallback( - (lineIndex: number): string => { - if (!needsScroll || !showScrollbar) return ''; - const isThumb = lineIndex >= thumbStart && lineIndex < thumbStart + thumbSize; - return isThumb ? SCROLLBAR_THUMB : SCROLLBAR_TRACK; - }, - [needsScroll, showScrollbar, thumbStart, thumbSize] - ); - - if (!content) { - return null; - } - - return ( - - - {/* Content area */} - - {visibleLines.map((line, idx) => ( - - {line || ' '} - - ))} - - - {/* Scrollbar */} - {needsScroll && showScrollbar && ( - - {Array.from({ length: displayHeight }).map((_, idx) => ( - - {renderScrollbar(idx)} - - ))} - - )} - - - {/* Status line */} - {needsScroll && ( - - [{effectiveOffset + 1}-{Math.min(effectiveOffset + displayHeight, totalLines)} of {totalLines}] ↑↓ PgUp/PgDn - - )} - - ); -} diff --git a/src/cli/tui/components/TwoColumn.tsx b/src/cli/tui/components/TwoColumn.tsx deleted file mode 100644 index a67ad51a9..000000000 --- a/src/cli/tui/components/TwoColumn.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useResponsive } from '../hooks/useResponsive'; -import { Box } from 'ink'; -import type { ReactNode } from 'react'; - -export function TwoColumn({ - left, - right, - marginTop, - collapseBelow = 80, - ratio = [1, 1], -}: { - left: ReactNode; - right?: ReactNode; - marginTop?: number; - collapseBelow?: number; - /** Flex ratio for [left, right] columns. Default is [1, 1] for equal sizing. */ - ratio?: [number, number]; -}) { - const { isNarrow, width } = useResponsive(); - const shouldStack = isNarrow || width < collapseBelow; - - if (!right) { - return {left}; - } - - return ( - - - {left} - - - {right} - - - ); -} diff --git a/src/cli/tui/components/__tests__/ScrollableText.test.tsx b/src/cli/tui/components/__tests__/ScrollableText.test.tsx deleted file mode 100644 index b81c766bd..000000000 --- a/src/cli/tui/components/__tests__/ScrollableText.test.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import { ScrollableText } from '../ScrollableText.js'; -import { render } from 'ink-testing-library'; -import React from 'react'; -import { afterEach, describe, expect, it, vi } from 'vitest'; - -const UP = '\x1B[A'; -const DOWN = '\x1B[B'; - -function delay(ms = 50) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -afterEach(() => vi.restoreAllMocks()); - -function makeContent(lineCount: number): string { - return Array.from({ length: lineCount }, (_, i) => `Line ${i + 1}`).join('\n'); -} - -describe('ScrollableText', () => { - it('returns null when content is empty', () => { - const { lastFrame } = render(); - - expect(lastFrame()).toBe(''); - }); - - it('renders all lines when content fits within height', () => { - const content = 'Line 1\nLine 2\nLine 3'; - const { lastFrame } = render(); - const frame = lastFrame()!; - - expect(frame).toContain('Line 1'); - expect(frame).toContain('Line 2'); - expect(frame).toContain('Line 3'); - }); - - it('does not show scrollbar when content fits', () => { - const content = 'Line 1\nLine 2'; - const { lastFrame } = render(); - const frame = lastFrame()!; - - expect(frame).not.toContain('\u2588'); // block char - expect(frame).not.toContain('\u2591'); // light shade - }); - - it('shows only height lines when content overflows', () => { - const content = makeContent(20); - const { lastFrame } = render(); - const frame = lastFrame()!; - - // Should show status line with scroll info - expect(frame).toContain('of 20'); - }); - - it('shows scrollbar when content overflows', () => { - const content = makeContent(30); - const { lastFrame } = render(); - const frame = lastFrame()!; - - // Scrollbar chars should appear - expect(frame).toMatch(/[█░]/); - }); - - it('hides scrollbar when showScrollbar is false', () => { - const content = makeContent(30); - const { lastFrame } = render(); - const frame = lastFrame()!; - - // Should still show status line but no scrollbar track - expect(frame).not.toContain('░'); - }); - - it('scrolls down with arrow key', async () => { - const content = makeContent(20); - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(DOWN); - await delay(); - - // After scrolling down, should no longer show from the top - const frame = lastFrame()!; - expect(frame).toContain('of 20'); - }); - - it('scrolls up with arrow key', async () => { - const content = makeContent(20); - const { lastFrame, stdin } = render(); - - await delay(); - // Scroll down first, then back up - stdin.write(DOWN); - stdin.write(DOWN); - await delay(); - stdin.write(UP); - await delay(); - - const frame = lastFrame()!; - expect(frame).toContain('of 20'); - }); - - it('auto-scrolls to bottom when streaming', () => { - const content = makeContent(20); - const { lastFrame } = render(); - const frame = lastFrame()!; - - // When streaming, should show the last lines - expect(frame).toContain('Line 20'); - }); - - it('shows status line with line range when scrolling needed', () => { - const content = makeContent(20); - const { lastFrame } = render(); - const frame = lastFrame()!; - - // Status line should show range and total - expect(frame).toMatch(/\[\d+-\d+ of 20\]/); - expect(frame).toContain('PgUp/PgDn'); - }); - - it('does not show status line when content fits', () => { - const content = 'Line 1\nLine 2'; - const { lastFrame } = render(); - - expect(lastFrame()).not.toContain('PgUp/PgDn'); - }); - - it('wraps long lines to fit terminal width', () => { - // Create a line longer than any reasonable terminal width - const longLine = 'A'.repeat(200); - const { lastFrame } = render(); - const frame = lastFrame()!; - - // Content should appear (wrapped) - expect(frame).toContain('A'); - }); - - it('does not respond to input when isActive is false', async () => { - const content = makeContent(20); - const { lastFrame, stdin } = render(); - - const before = lastFrame(); - await delay(); - stdin.write(DOWN); - stdin.write(DOWN); - await delay(); - - // Frame shouldn't change since input is disabled - expect(lastFrame()).toBe(before); - }); -}); diff --git a/src/cli/tui/components/__tests__/TwoColumn.test.tsx b/src/cli/tui/components/__tests__/TwoColumn.test.tsx deleted file mode 100644 index e7cc96ec1..000000000 --- a/src/cli/tui/components/__tests__/TwoColumn.test.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { TwoColumn } from '../TwoColumn.js'; -import { Text } from 'ink'; -import { render } from 'ink-testing-library'; -import React from 'react'; -import { afterEach, describe, expect, it, vi } from 'vitest'; - -const { mockWidth, mockIsNarrow } = vi.hoisted(() => ({ - mockWidth: { value: 120 }, - mockIsNarrow: { value: false }, -})); - -vi.mock('../../hooks/useResponsive.js', () => ({ - useResponsive: () => ({ - width: mockWidth.value, - height: 40, - isNarrow: mockIsNarrow.value, - }), -})); - -afterEach(() => { - mockWidth.value = 120; - mockIsNarrow.value = false; -}); - -describe('TwoColumn', () => { - it('renders both left and right content on wide screen', () => { - const { lastFrame } = render(LEFT_MARKER} right={RIGHT_MARKER} />); - const frame = lastFrame()!; - expect(frame).toContain('LEFT_MARKER'); - expect(frame).toContain('RIGHT_MARKER'); - // On wide screen, both should be on the same line (side by side) - const lines = frame.split('\n'); - const lineWithLeft = lines.find(l => l.includes('LEFT_MARKER')); - expect(lineWithLeft).toContain('RIGHT_MARKER'); - }); - - it('stacks columns vertically on narrow screen', () => { - mockIsNarrow.value = true; - mockWidth.value = 40; - - const { lastFrame } = render(LEFT_MARKER} right={RIGHT_MARKER} />); - const frame = lastFrame()!; - expect(frame).toContain('LEFT_MARKER'); - expect(frame).toContain('RIGHT_MARKER'); - // On narrow screen, left and right should be on different lines (stacked) - const lines = frame.split('\n'); - const lineWithLeft = lines.find(l => l.includes('LEFT_MARKER')); - expect(lineWithLeft).not.toContain('RIGHT_MARKER'); - }); - - it('renders only left content when no right provided', () => { - const { lastFrame } = render(Only left} />); - expect(lastFrame()).toContain('Only left'); - }); - - it('stacks when width is below collapseBelow threshold', () => { - mockWidth.value = 60; - mockIsNarrow.value = false; - - const { lastFrame } = render( - LEFT_MARKER} right={RIGHT_MARKER} collapseBelow={80} /> - ); - // Width 60 < collapseBelow 80 → stacked - const lines = lastFrame()!.split('\n'); - const lineWithLeft = lines.find(l => l.includes('LEFT_MARKER')); - expect(lineWithLeft).not.toContain('RIGHT_MARKER'); - }); - - it('shows side-by-side when width exceeds collapseBelow', () => { - mockWidth.value = 120; - mockIsNarrow.value = false; - - const { lastFrame } = render( - LEFT_MARKER} right={RIGHT_MARKER} collapseBelow={80} /> - ); - // Width 120 > collapseBelow 80 → side by side - const lines = lastFrame()!.split('\n'); - const lineWithLeft = lines.find(l => l.includes('LEFT_MARKER')); - expect(lineWithLeft).toContain('RIGHT_MARKER'); - }); - - it('renders both columns with custom ratio prop', () => { - mockWidth.value = 120; - mockIsNarrow.value = false; - - const { lastFrame } = render( - LEFT_MARKER} right={RIGHT_MARKER} ratio={[3, 1]} /> - ); - // Both columns should still render side-by-side with a 3:1 ratio - const lines = lastFrame()!.split('\n'); - const lineWithLeft = lines.find(l => l.includes('LEFT_MARKER')); - expect(lineWithLeft).toContain('RIGHT_MARKER'); - }); -}); diff --git a/src/cli/tui/components/index.ts b/src/cli/tui/components/index.ts index a2c5aefb8..15910ceba 100644 --- a/src/cli/tui/components/index.ts +++ b/src/cli/tui/components/index.ts @@ -5,7 +5,7 @@ export { Cursor } from './Cursor'; export { DeployStatus } from './DeployStatus'; export { FatalError, type FatalErrorProps } from './FatalError'; export { Header } from './Header'; -export { HelpText, ExitHelpText } from './HelpText'; +export { ExitHelpText } from './HelpText'; export { LogPanel, type LogEntry } from './LogPanel'; export { FullScreenLogView } from './FullScreenLogView'; export { Panel, type PanelProps } from './Panel'; @@ -21,10 +21,8 @@ export { SelectScreen } from './SelectScreen'; export { StepIndicator } from './StepIndicator'; export { StepProgress, GradientText, type Step, type StepStatus, hasStepError, areStepsComplete } from './StepProgress'; export { TextInput } from './TextInput'; -export { TwoColumn } from './TwoColumn'; export { WizardSelect, WizardMultiSelect } from './WizardSelect'; export { AwsTargetConfigUI, getAwsConfigHelpText } from './AwsTargetConfigUI'; export { ResourceGraph } from './ResourceGraph'; export { LogLink } from './LogLink'; -export { ScrollableText } from './ScrollableText'; export { DiffSummaryView, parseStackDiff, parseDiffResult, type StackDiffSummary } from './DiffSummaryView'; diff --git a/src/cli/tui/hooks/__tests__/usePanelNavigation.test.tsx b/src/cli/tui/hooks/__tests__/usePanelNavigation.test.tsx deleted file mode 100644 index 5df2685fa..000000000 --- a/src/cli/tui/hooks/__tests__/usePanelNavigation.test.tsx +++ /dev/null @@ -1,347 +0,0 @@ -import { usePanelNavigation } from '../usePanelNavigation.js'; -import { Text } from 'ink'; -import { render } from 'ink-testing-library'; -import React from 'react'; -import { afterEach, describe, expect, it, vi } from 'vitest'; - -const UP_ARROW = '\x1B[A'; -const DOWN_ARROW = '\x1B[B'; -const ENTER = '\r'; -const ESCAPE = '\x1B'; -const TAB = '\t'; - -afterEach(() => vi.restoreAllMocks()); - -// Wrapper component to test the hook via rendering -function PanelNav({ - isActive = true, - fieldCount = 3, - onExit = vi.fn(), - isFieldDisabled, - isFieldAutoCompleted, - onComplete, - onResult, -}: { - isActive?: boolean; - fieldCount?: number; - onExit?: () => void; - isFieldDisabled?: (column: number, field: number) => boolean; - isFieldAutoCompleted?: (column: number, field: number) => boolean; - onComplete?: () => void; - onResult?: (result: ReturnType) => void; -}) { - const result = usePanelNavigation({ - isActive, - fieldCount, - onExit, - isFieldDisabled, - isFieldAutoCompleted, - onComplete, - }); - - onResult?.(result); - - return ( - - col:{result.position.column} field:{result.position.field} layer:{result.position.layer} - - ); -} - -const delay = (ms = 50) => new Promise(resolve => setTimeout(resolve, ms)); - -describe('usePanelNavigation', () => { - it('starts at column 0, field 0, layer focus', () => { - const { lastFrame } = render(); - expect(lastFrame()).toContain('col:0'); - expect(lastFrame()).toContain('field:0'); - expect(lastFrame()).toContain('layer:focus'); - }); - - describe('Tab switches columns', () => { - it('Tab switches from column 0 to column 1', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(TAB); - await delay(); - - expect(lastFrame()).toContain('col:1'); - }); - - it('Tab switches from column 1 back to column 0', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(TAB); // 0 → 1 - await delay(); - stdin.write(TAB); // 1 → 0 - await delay(); - - expect(lastFrame()).toContain('col:0'); - }); - }); - - describe('Up/Down moves between fields', () => { - it('Down moves to next field', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(DOWN_ARROW); - await delay(); - - expect(lastFrame()).toContain('field:1'); - }); - - it('Up moves to previous field', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(DOWN_ARROW); - stdin.write(DOWN_ARROW); - await delay(); - expect(lastFrame()).toContain('field:2'); - - stdin.write(UP_ARROW); - await delay(); - expect(lastFrame()).toContain('field:1'); - }); - }); - - it('Up at field 0 stays at field 0', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(UP_ARROW); - await delay(); - - expect(lastFrame()).toContain('field:0'); - }); - - it('Down at last field stays at last field', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(DOWN_ARROW); - stdin.write(DOWN_ARROW); // field 2 (last) - await delay(); - expect(lastFrame()).toContain('field:2'); - - stdin.write(DOWN_ARROW); // should stay - await delay(); - expect(lastFrame()).toContain('field:2'); - }); - - it('Enter activates field (layer → active)', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(ENTER); - await delay(); - - expect(lastFrame()).toContain('layer:active'); - }); - - describe('Escape navigation', () => { - it('Escape at field 0 column 0 calls onExit', async () => { - const onExit = vi.fn(); - const { stdin } = render(); - - await delay(); - stdin.write(ESCAPE); - await delay(); - - expect(onExit).toHaveBeenCalledTimes(1); - }); - - it('Escape at field > 0 goes to field 0', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(DOWN_ARROW); - stdin.write(DOWN_ARROW); - await delay(); - expect(lastFrame()).toContain('field:2'); - - stdin.write(ESCAPE); - await delay(); - expect(lastFrame()).toContain('field:0'); - }); - - it('Escape at column 1 field 0 goes to column 0', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(TAB); // go to column 1 - await delay(); - expect(lastFrame()).toContain('col:1'); - - stdin.write(ESCAPE); - await delay(); - expect(lastFrame()).toContain('col:0'); - expect(lastFrame()).toContain('field:0'); - }); - }); - - describe('deactivate auto-advance', () => { - it('deactivate auto-advances to next field in same column', async () => { - const onResult = vi.fn(); - const { stdin } = render(); - - await delay(); - stdin.write(ENTER); // activate field 0 - await delay(); - - const result = onResult.mock.calls[onResult.mock.calls.length - 1]![0]; - expect(result.position.layer).toBe('active'); - }); - }); - - describe('deactivate behavior', () => { - // Harness that auto-deactivates when activated to test the deactivate advance path - function AutoDeactivateHarness({ fieldCount = 3, onComplete }: { fieldCount?: number; onComplete?: () => void }) { - const nav = usePanelNavigation({ - isActive: true, - fieldCount, - onExit: vi.fn(), - onComplete, - }); - - // When activated, immediately deactivate on next render - React.useEffect(() => { - if (nav.position.layer === 'active') { - nav.deactivate(); - } - }, [nav]); - - return ( - - col:{nav.position.column} field:{nav.position.field} layer:{nav.position.layer} - - ); - } - - it('deactivate at field 0 advances to field 1 in same column', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(ENTER); // activate field 0 → auto-deactivate → field 1 - await delay(); - - expect(lastFrame()).toContain('field:1'); - expect(lastFrame()).toContain('col:0'); - expect(lastFrame()).toContain('layer:focus'); - }); - - it('deactivate at last field of column 0 moves to column 1 field 0', async () => { - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(ENTER); // activate field 0 (last in col 0) → auto-deactivate → col 1 field 0 - await delay(); - - expect(lastFrame()).toContain('col:1'); - expect(lastFrame()).toContain('field:0'); - }); - - it('deactivate at last field of column 1 calls onComplete', async () => { - const onComplete = vi.fn(); - const { lastFrame, stdin } = render(); - - await delay(); - // Move to column 1 first - stdin.write(ENTER); // col 0 field 0 → deactivate → col 1 field 0 - await delay(); - - expect(lastFrame()).toContain('col:1'); - expect(lastFrame()).toContain('field:0'); - - stdin.write(ENTER); // col 1 field 0 (last) → deactivate → onComplete - await delay(100); - - expect(onComplete).toHaveBeenCalled(); - }); - }); - - describe('isFieldFocused/isFieldActive/isColumnActive', () => { - it('isFieldFocused returns true for current position in focus layer', () => { - let resultRef: ReturnType | undefined; - render( - { - resultRef = r; - }} - /> - ); - - expect(resultRef!.isFieldFocused(0, 0)).toBe(true); - expect(resultRef!.isFieldFocused(0, 1)).toBe(false); - expect(resultRef!.isFieldFocused(1, 0)).toBe(false); - }); - - it('isFieldActive returns false in focus layer', () => { - let resultRef: ReturnType | undefined; - render( - { - resultRef = r; - }} - /> - ); - - expect(resultRef!.isFieldActive(0, 0)).toBe(false); - }); - - it('isColumnActive returns true for current column', () => { - let resultRef: ReturnType | undefined; - render( - { - resultRef = r; - }} - /> - ); - - expect(resultRef!.isColumnActive(0)).toBe(true); - expect(resultRef!.isColumnActive(1)).toBe(false); - }); - }); - - describe('disabled fields are skipped', () => { - it('Down skips disabled field', async () => { - const isFieldDisabled = (_col: number, field: number) => field === 1; - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(DOWN_ARROW); // should skip field 1 and land on field 2 - await delay(); - - expect(lastFrame()).toContain('field:2'); - }); - - it('Up skips disabled field', async () => { - const isFieldDisabled = (_col: number, field: number) => field === 1; - const { lastFrame, stdin } = render(); - - await delay(); - stdin.write(DOWN_ARROW); // skip 1 → field 2 - await delay(); - expect(lastFrame()).toContain('field:2'); - - stdin.write(UP_ARROW); // skip 1 → field 0 - await delay(); - expect(lastFrame()).toContain('field:0'); - }); - - it('stays in place when all remaining fields are disabled', async () => { - const { lastFrame, stdin } = render( f === 1} />); - - await delay(); - // field 0, only field 1 exists and is disabled → stay at 0 - stdin.write(DOWN_ARROW); - await delay(); - - expect(lastFrame()).toContain('field:0'); - }); - }); -}); diff --git a/src/cli/tui/hooks/use-gradient.ts b/src/cli/tui/hooks/use-gradient.ts deleted file mode 100644 index 3dc489f1d..000000000 --- a/src/cli/tui/hooks/use-gradient.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useEffect, useState } from 'react'; - -/** - * Creates a gradient shimmer effect for text - useful for showing ongoing progress - * Currently unused but kept for potential future use in progress indicators - */ -export function useGradient(text: string, enabled: boolean) { - const [phase, setPhase] = useState(0); - - useEffect(() => { - if (!enabled) return; - - // Increment the phase to create the "ongoing" movement - const id = setInterval(() => { - setPhase(p => p + 0.1); - }, 75); - - return () => clearInterval(id); - }, [enabled]); - - if (!enabled) return text; - - return text.split('').map((ch, i) => { - // Math to create the "moving" shimmer - const t = phase + i * 0.4; - const wave = (Math.sin(t) + 1) / 2; - - // Subtle Professional Yellow Hue - const r = Math.round(210 + wave * 45); - const g = Math.round(170 + wave * 40); - const b = Math.round(40 + wave * 30); - - return { - char: ch, - color: `rgb(${r},${g},${b})`, - }; - }); -} diff --git a/src/cli/tui/hooks/useAttach.ts b/src/cli/tui/hooks/useAttach.ts deleted file mode 100644 index c468894d3..000000000 --- a/src/cli/tui/hooks/useAttach.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { - bindMcpRuntimeToAgent, - getAvailableAgents, - getCredentials, - getGateways, - getMcpRuntimeTools, - getMemories, -} from '../../operations/attach'; -import type { BindMcpRuntimeConfig } from '../../operations/attach'; -import { useCallback, useEffect, useState } from 'react'; - -// ───────────────────────────────────────────────────────────────────────────── -// Data Loading Hooks -// ───────────────────────────────────────────────────────────────────────────── - -export function useAgents() { - const [agents, setAgents] = useState(null); - - useEffect(() => { - void getAvailableAgents().then(setAgents); - }, []); - - const refresh = useCallback(async () => { - const result = await getAvailableAgents(); - setAgents(result); - }, []); - - return { agents: agents ?? [], isLoading: agents === null, refresh }; -} - -export function useMemories() { - const [memories, setMemories] = useState([]); - - useEffect(() => { - void getMemories().then(setMemories); - }, []); - - const refresh = useCallback(async () => { - const result = await getMemories(); - setMemories(result); - }, []); - - return { memories, refresh }; -} - -export function useCredentials() { - const [credentials, setCredentials] = useState([]); - - useEffect(() => { - void getCredentials().then(setCredentials); - }, []); - - const refresh = useCallback(async () => { - const result = await getCredentials(); - setCredentials(result); - }, []); - - return { credentials, refresh }; -} - -export function useMcpRuntimeTools() { - const [tools, setTools] = useState([]); - - useEffect(() => { - void getMcpRuntimeTools().then(setTools); - }, []); - - const refresh = useCallback(async () => { - const result = await getMcpRuntimeTools(); - setTools(result); - }, []); - - return { tools, refresh }; -} - -export function useGateways() { - const [gateways, setGateways] = useState([]); - - useEffect(() => { - void getGateways().then(setGateways); - }, []); - - const refresh = useCallback(async () => { - const result = await getGateways(); - setGateways(result); - }, []); - - return { gateways, refresh }; -} - -// ───────────────────────────────────────────────────────────────────────────── -// Legacy hooks (stubs for TUI screens, v2 doesn't use attach pattern) -// ───────────────────────────────────────────────────────────────────────────── - -// Alias for old hook name -export const useOwnedMemories = useMemories; -export const useOwnedIdentities = useCredentials; - -// Stub attach hooks (no-op in v2, resources have implicit access) -const noop = () => { - /* no-op */ -}; - -export function useAttachAgent() { - return { - attach: () => Promise.resolve({ ok: true as const }), - isLoading: false, - reset: noop, - }; -} - -export function useAttachMemory() { - return { - attach: () => Promise.resolve({ ok: true as const }), - isLoading: false, - reset: noop, - }; -} - -export function useAttachIdentity() { - return { - attach: () => Promise.resolve({ ok: true as const }), - isLoading: false, - reset: noop, - }; -} - -export function useAttachGateway() { - return { - attach: () => Promise.resolve({ ok: true as const }), - isLoading: false, - reset: noop, - }; -} - -// ───────────────────────────────────────────────────────────────────────────── -// MCP Binding Hook -// ───────────────────────────────────────────────────────────────────────────── - -export function useBindMcpRuntime() { - const [isLoading, setIsLoading] = useState(false); - - const bind = useCallback(async (mcpRuntimeName: string, config: BindMcpRuntimeConfig) => { - setIsLoading(true); - try { - await bindMcpRuntimeToAgent(mcpRuntimeName, config); - return { ok: true as const }; - } catch (err) { - const message = err instanceof Error ? err.message : 'Failed to bind agent to MCP runtime.'; - return { ok: false as const, error: message }; - } finally { - setIsLoading(false); - } - }, []); - - const reset = useCallback(() => setIsLoading(false), []); - - return { bind, isLoading, reset }; -} diff --git a/src/cli/tui/hooks/usePanelNavigation.ts b/src/cli/tui/hooks/usePanelNavigation.ts deleted file mode 100644 index 1e06157ac..000000000 --- a/src/cli/tui/hooks/usePanelNavigation.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { useInput } from 'ink'; -import { useCallback, useState } from 'react'; - -export interface PanelPosition { - column: 0 | 1; - field: number; - layer: 'focus' | 'active'; -} - -interface UsePanelNavigationOptions { - /** Only capture input when the builder step is active */ - isActive: boolean; - /** Number of fields per column */ - fieldCount: number; - /** Called when Escape is pressed at the top-left origin */ - onExit: () => void; - /** Optional check whether a field is disabled (non-focusable) */ - isFieldDisabled?: (column: number, field: number) => boolean; - /** Optional check whether a field is auto-completed (skip on navigation) */ - isFieldAutoCompleted?: (column: number, field: number) => boolean; - /** Called when the last field in the last column is completed */ - onComplete?: () => void; -} - -interface UsePanelNavigationResult { - position: PanelPosition; - /** Whether the given field is the currently focused field */ - isFieldFocused: (column: number, field: number) => boolean; - /** Whether the given field has its picker/input open */ - isFieldActive: (column: number, field: number) => boolean; - /** Whether the given column is the active column */ - isColumnActive: (column: number) => boolean; - /** Open the picker/input for the currently focused field */ - activate: () => void; - /** Close the picker/input, returning to field focus */ - deactivate: () => void; - /** Move focus to a specific field */ - moveToField: (column: number, field: number) => void; -} - -/** - * 2D focus management hook for a side-by-side panel builder. - * - * Navigation model: - * - Tab switches columns (0 <-> 1) - * - Up/Down moves between fields within the active column - * - Enter activates the focused field (layer -> 'active') - * - Escape deactivates or navigates back - * - * When layer === 'active', the hook yields input to child components - * by setting its own `useInput` to inactive. - */ -export function usePanelNavigation({ - isActive, - fieldCount, - onExit, - isFieldDisabled, - isFieldAutoCompleted: _isFieldAutoCompleted, - onComplete, -}: UsePanelNavigationOptions): UsePanelNavigationResult { - const [position, setPosition] = useState({ - column: 0, - field: 0, - layer: 'focus', - }); - - // Only handle input when at focus layer and the panel is active - const inputActive = isActive && position.layer === 'focus'; - - useInput( - (input, key) => { - // Tab: switch columns - if (key.tab) { - setPosition(p => ({ - ...p, - column: p.column === 0 ? 1 : 0, - })); - return; - } - - // Up: move to previous field - if (key.upArrow) { - setPosition(p => { - let next = p.field - 1; - // Skip disabled fields going up - while (next >= 0 && isFieldDisabled?.(p.column, next)) { - next--; - } - if (next < 0) return p; - return { ...p, field: next }; - }); - return; - } - - // Down: move to next field - if (key.downArrow) { - setPosition(p => { - let next = p.field + 1; - // Skip disabled fields going down - while (next < fieldCount && isFieldDisabled?.(p.column, next)) { - next++; - } - if (next >= fieldCount) return p; - return { ...p, field: next }; - }); - return; - } - - // Enter: always activate the focused field (open picker) - if (key.return) { - setPosition(p => ({ ...p, layer: 'active' })); - return; - } - - // Escape: navigate back through the hierarchy - if (key.escape) { - setPosition(p => { - // If not at field 0, go to field 0 in same column - if (p.field > 0) { - return { ...p, field: 0 }; - } - // If at field 0 but not column 0, go to column 0 - if (p.column > 0) { - return { ...p, column: 0 }; - } - // At origin: exit - onExit(); - return p; - }); - return; - } - }, - { isActive: inputActive } - ); - - const isFieldFocused = useCallback( - (column: number, field: number): boolean => { - return position.column === column && position.field === field && position.layer === 'focus'; - }, - [position] - ); - - const isFieldActive = useCallback( - (column: number, field: number): boolean => { - return position.column === column && position.field === field && position.layer === 'active'; - }, - [position] - ); - - const isColumnActive = useCallback( - (column: number): boolean => { - return position.column === column; - }, - [position.column] - ); - - const activate = useCallback(() => { - setPosition(p => ({ ...p, layer: 'active' })); - }, []); - - const deactivate = useCallback(() => { - setPosition(p => { - // After a selection, advance to the next field in sequence: - // column 0 fields 0→1→2, then column 1 fields 0→1→2, then complete - const nextField = p.field + 1; - if (nextField < fieldCount) { - // Next field in same column - return { column: p.column, field: nextField, layer: 'focus' }; - } - if (p.column === 0) { - // Finished left column → move to right column field 0 - return { column: 1, field: 0, layer: 'focus' }; - } - // Finished last field in right column → stay and let onComplete handle it - if (onComplete) { - // Use setTimeout to avoid setState during render - setTimeout(onComplete, 0); - } - return { ...p, layer: 'focus' }; - }); - }, [fieldCount, onComplete]); - - const moveToField = useCallback((column: number, field: number) => { - setPosition({ column: column as 0 | 1, field, layer: 'focus' }); - }, []); - - return { - position, - isFieldFocused, - isFieldActive, - isColumnActive, - activate, - deactivate, - moveToField, - }; -} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 5ef7b5c34..a58bfd194 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -10,9 +10,6 @@ export const CONFIG_DIR = 'agentcore'; export const APP_DIR = 'app'; export const MCP_APP_SUBDIR = 'mcp'; -// Harnesses directory -export const HARNESS_DIR = 'harnesses'; - // CLI system subdirectory (inside CONFIG_DIR) export const CLI_SYSTEM_DIR = '.cli'; export const CLI_LOGS_DIR = 'logs'; @@ -38,7 +35,6 @@ export function getArtifactZipName(name: string): string { export const UV_INSTALL_HINT = 'Install uv from https://github.com/astral-sh/uv#installation and ensure it is on your PATH.'; -export const NPM_INSTALL_HINT = 'Install npm from https://nodejs.org/ and ensure it is on your PATH.'; export const DEFAULT_PYTHON_PLATFORM = 'aarch64-manylinux2014'; // Container constants diff --git a/src/lib/packaging/helpers.ts b/src/lib/packaging/helpers.ts index e45c86301..8e59b872b 100644 --- a/src/lib/packaging/helpers.ts +++ b/src/lib/packaging/helpers.ts @@ -2,7 +2,7 @@ import type { RuntimeVersion } from '../../schema'; import { CONFIG_DIR } from '../constants'; import { ArtifactSizeError, MissingDependencyError, MissingProjectFileError } from '../errors/types'; import { isWindows } from '../utils/platform'; -import { checkSubprocess, checkSubprocessSync, runSubprocess } from '../utils/subprocess'; +import { checkSubprocess, checkSubprocessSync } from '../utils/subprocess'; import type { PackageOptions } from './types/packaging'; import type { Zippable } from 'fflate'; import { zipSync } from 'fflate'; @@ -225,10 +225,6 @@ export async function ensureBinaryAvailable(binary: string, installHint?: string throw new MissingDependencyError(binary, installHint); } -export async function runCommand(command: string, args: string[], cwd?: string): Promise { - await runSubprocess(command, args, { cwd }); -} - export async function createZipFromDir(sourceDir: string, outputZip: string): Promise { await rm(outputZip, { force: true }); await mkdir(dirname(outputZip), { recursive: true }); diff --git a/src/lib/packaging/types/archiver.d.ts b/src/lib/packaging/types/archiver.d.ts deleted file mode 100644 index 5fa580c26..000000000 --- a/src/lib/packaging/types/archiver.d.ts +++ /dev/null @@ -1,42 +0,0 @@ -declare module 'archiver' { - import type { Stats } from 'fs'; - import type { Writable } from 'stream'; - - export interface ArchiverOptions { - zlib?: { - level?: number; - }; - } - - export interface EntryData { - name?: string; - prefix?: string; - stats?: Stats; - } - - export interface ProgressData { - entries: { - total: number; - processed: number; - }; - fs?: { - totalBytes?: number; - processedBytes?: number; - }; - } - - export interface Archiver { - directory(source: string, destination?: string | false, data?: EntryData): Archiver; - pipe(stream: Writable): Writable; - finalize(): Promise; - on(event: 'warning', handler: (error: NodeJS.ErrnoException) => void): this; - on(event: 'error', handler: (error: Error) => void): this; - on(event: 'finish', handler: () => void): this; - on(event: 'end', handler: () => void): this; - on(event: 'close', handler: () => void): this; - on(event: 'entry', handler: (entry: EntryData) => void): this; - on(event: 'progress', handler: (data: ProgressData) => void): this; - } - - export default function archiver(format: 'zip', options?: ArchiverOptions): Archiver; -} diff --git a/src/lib/utils/__tests__/platform.test.ts b/src/lib/utils/__tests__/platform.test.ts index 8f02c7b27..91fe9a0b2 100644 --- a/src/lib/utils/__tests__/platform.test.ts +++ b/src/lib/utils/__tests__/platform.test.ts @@ -1,5 +1,5 @@ -import { getShellArgs, getShellCommand, getVenvExecutable, normalizeCommand } from '../platform.js'; -import { afterEach, describe, expect, it, vi } from 'vitest'; +import { getVenvExecutable } from '../platform.js'; +import { describe, expect, it } from 'vitest'; describe('getVenvExecutable', () => { it('returns bin path on unix', () => { @@ -13,85 +13,3 @@ describe('getVenvExecutable', () => { expect(result).toContain('uvicorn'); }); }); - -describe('getShellCommand', () => { - it('returns a string', () => { - const result = getShellCommand(); - expect(typeof result).toBe('string'); - expect(result.length).toBeGreaterThan(0); - }); -}); - -describe('getShellArgs', () => { - it('wraps command with shell flag', () => { - const args = getShellArgs('echo hello'); - expect(args).toHaveLength(2); - expect(args[1]).toBe('echo hello'); - }); -}); - -describe('normalizeCommand', () => { - it('returns command unchanged on non-Windows', () => { - expect(normalizeCommand('python')).toBe('python'); - expect(normalizeCommand('node')).toBe('node'); - expect(normalizeCommand('npm')).toBe('npm'); - }); - - it('preserves commands that already have .exe extension', () => { - expect(normalizeCommand('python.exe')).toBe('python.exe'); - }); - - it('preserves commands that already have .cmd extension', () => { - expect(normalizeCommand('npm.cmd')).toBe('npm.cmd'); - }); - - it('preserves commands that already have .bat extension', () => { - expect(normalizeCommand('run.bat')).toBe('run.bat'); - }); - - it('returns unknown commands unchanged', () => { - expect(normalizeCommand('custom-tool')).toBe('custom-tool'); - expect(normalizeCommand('my-script')).toBe('my-script'); - }); -}); - -describe('normalizeCommand (Windows behavior)', () => { - const originalPlatform = process.platform; - - afterEach(() => { - Object.defineProperty(process, 'platform', { value: originalPlatform, writable: true }); - vi.resetModules(); - }); - - it('appends .exe to known commands on Windows', async () => { - vi.resetModules(); - Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); - const { normalizeCommand: normalizeWin } = await import('../platform.js'); - - expect(normalizeWin('python')).toBe('python.exe'); - expect(normalizeWin('node')).toBe('node.exe'); - expect(normalizeWin('npm')).toBe('npm.exe'); - expect(normalizeWin('git')).toBe('git.exe'); - expect(normalizeWin('uvicorn')).toBe('uvicorn.exe'); - expect(normalizeWin('pip')).toBe('pip.exe'); - }); - - it('does not append .exe to commands already with extensions on Windows', async () => { - vi.resetModules(); - Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); - const { normalizeCommand: normalizeWin } = await import('../platform.js'); - - expect(normalizeWin('python.exe')).toBe('python.exe'); - expect(normalizeWin('npm.cmd')).toBe('npm.cmd'); - expect(normalizeWin('run.bat')).toBe('run.bat'); - }); - - it('does not append .exe to unknown commands on Windows', async () => { - vi.resetModules(); - Object.defineProperty(process, 'platform', { value: 'win32', writable: true }); - const { normalizeCommand: normalizeWin } = await import('../platform.js'); - - expect(normalizeWin('custom-tool')).toBe('custom-tool'); - expect(normalizeWin('my-script')).toBe('my-script'); - }); -}); diff --git a/src/lib/utils/platform.ts b/src/lib/utils/platform.ts index 3232de8cd..0bb2f0d23 100644 --- a/src/lib/utils/platform.ts +++ b/src/lib/utils/platform.ts @@ -14,7 +14,6 @@ import { join } from 'node:path'; export const isWindows = process.platform === 'win32'; export const isMacOS = process.platform === 'darwin'; -export const isLinux = process.platform === 'linux'; /** * Get the path to an executable in a Python virtual environment. @@ -39,48 +38,3 @@ export function getVenvExecutable(venvPath: string, executable: string): string const ext = isWindows ? '.exe' : ''; return join(venvPath, binDir, executable + ext); } - -/** - * Get the appropriate shell command for the current platform. - * - * @returns The default shell command ('cmd' on Windows, 'sh' on Unix) - */ -export function getShellCommand(): string { - return isWindows ? 'cmd' : (process.env.SHELL ?? '/bin/sh'); -} - -/** - * Get the appropriate shell arguments for executing a command. - * - * @param command - The command to execute - * @returns Array of arguments to pass to the shell - * - * @example - * ```ts - * // On Unix: ['-c', 'echo hello'] - * // On Windows: ['/c', 'echo hello'] - * const args = getShellArgs('echo hello'); - * spawn(getShellCommand(), args); - * ``` - */ -export function getShellArgs(command: string): string[] { - return isWindows ? ['/c', command] : ['-c', command]; -} - -/** - * Normalize a command for cross-platform execution. - * Adds .exe extension on Windows if needed. - * - * @param command - The command name - * @returns The command with appropriate extension - */ -export function normalizeCommand(command: string): string { - if (isWindows && !command.endsWith('.exe') && !command.endsWith('.cmd') && !command.endsWith('.bat')) { - // Check if it's a known command that needs .exe - const exeCommands = ['python', 'node', 'npm', 'git', 'uvicorn', 'pip']; - if (exeCommands.some(cmd => command.endsWith(cmd))) { - return command + '.exe'; - } - } - return command; -} diff --git a/src/schema/schemas/primitives/harness.ts b/src/schema/schemas/primitives/harness.ts index c165e933e..e4bc6cdd3 100644 --- a/src/schema/schemas/primitives/harness.ts +++ b/src/schema/schemas/primitives/harness.ts @@ -514,7 +514,6 @@ export const HarnessSkillSchema = z.union([ HarnessSkillAwsSkillsSourceSchema, ]); -export type HarnessSkillInput = z.input; export type HarnessSkill = z.output; // ============================================================================ diff --git a/src/schema/schemas/primitives/index.ts b/src/schema/schemas/primitives/index.ts deleted file mode 100644 index d988ee49f..000000000 --- a/src/schema/schemas/primitives/index.ts +++ /dev/null @@ -1,121 +0,0 @@ -export type { - ABTest, - ABTestVariant, - ABTestEvaluationConfig, - ConfigurationBundleRef, - TrafficAllocationConfig, - VariantConfiguration, -} from './ab-test'; - -export type { Dataset, DatasetSchemaType } from './dataset'; -export { DatasetNameSchema, DatasetSchema, DatasetSchemaTypeSchema } from './dataset'; -export { - ABTestNameSchema, - ABTestDescriptionSchema, - ABTestSchema, - ABTestVariantSchema, - ABTestEvaluationConfigSchema, - ConfigurationBundleRefSchema, - TrafficAllocationConfigSchema, - VariantConfigurationSchema, - VariantNameSchema, - VariantWeightSchema, -} from './ab-test'; - -export type { MemoryStrategy, MemoryStrategyType } from './memory'; -export { - DEFAULT_EPISODIC_REFLECTION_NAMESPACE_TEMPLATES, - DEFAULT_EPISODIC_REFLECTION_NAMESPACES, - DEFAULT_STRATEGY_NAMESPACE_TEMPLATES, - DEFAULT_STRATEGY_NAMESPACES, - MemoryStrategyNameSchema, - MemoryStrategySchema, - MemoryStrategyTypeSchema, -} from './memory'; - -export type { - CategoricalRating, - CodeBasedConfig, - EvaluationLevel, - EvaluatorConfig, - ExternalCodeBasedConfig, - LlmAsAJudgeConfig, - ManagedCodeBasedConfig, - NumericalRating, - RatingScale, -} from './evaluator'; -export { - BedrockModelIdSchema, - CategoricalRatingSchema, - CodeBasedConfigSchema, - EvaluationLevelSchema, - EvaluatorConfigSchema, - EvaluatorNameSchema, - ExternalCodeBasedConfigSchema, - isValidBedrockModelId, - LlmAsAJudgeConfigSchema, - ManagedCodeBasedConfigSchema, - NumericalRatingSchema, - RatingScaleSchema, -} from './evaluator'; - -export type { OnlineEvalConfig, ClusteringConfig } from './online-eval-config'; -export { OnlineEvalConfigSchema, OnlineEvalConfigNameSchema, ClusteringConfigSchema } from './online-eval-config'; - -export type { AuthorizationPhase, EnforcementMode, Policy, PolicyEngine, ValidationMode } from './policy'; -export { - AuthorizationPhaseSchema, - EnforcementModeSchema, - PolicyEngineNameSchema, - PolicyEngineSchema, - PolicyNameSchema, - PolicySchema, - ValidationModeSchema, -} from './policy'; - -export type { - BedrockApiFormat, - HarnessApiFormat, - HarnessGatewayOutboundAuth, - HarnessMemoryRef, - HarnessModel, - HarnessModelProvider, - HarnessSpec, - HarnessTool, - HarnessToolType, - HarnessTruncationConfig, - ManagedMemoryStrategy, - OpenAiApiFormat, -} from './harness'; -export { - AllowedToolSchema, - BedrockApiFormatSchema, - HarnessApiFormatSchema, - OpenAiApiFormatSchema, - GatewayOAuthGrantTypeSchema, - HarnessGatewayOutboundAuthSchema, - HarnessMemoryRefSchema, - HarnessModelProviderSchema, - HarnessModelSchema, - HarnessNameSchema, - HarnessSpecSchema, - HarnessToolConfigSchema, - HarnessToolNameSchema, - HarnessToolSchema, - HarnessToolTypeSchema, - HarnessTruncationConfigSchema, - HarnessTruncationStrategySchema, - ManagedMemoryStrategySchema, -} from './harness'; - -export type { PaymentManager, PaymentConnector, PaymentProvider, PaymentAuthorizerType } from './payment'; -export { - DEFAULT_AUTO_PAYMENT, - DEFAULT_SPEND_LIMIT, - PaymentManagerSchema, - PaymentManagerNameSchema, - PaymentConnectorSchema, - PaymentConnectorNameSchema, - PaymentProviderSchema, - PaymentAuthorizerTypeSchema, -} from './payment'; diff --git a/src/tui-harness/index.ts b/src/tui-harness/index.ts index c74eafff0..46eee6a37 100644 --- a/src/tui-harness/index.ts +++ b/src/tui-harness/index.ts @@ -21,7 +21,7 @@ export { SPECIAL_KEY_VALUES, WaitForTimeoutError, LaunchError } from './lib/type export { KEY_MAP, resolveKey } from './lib/key-map.js'; // --- Availability --- -export { isAvailable, unavailableReason } from './lib/availability.js'; +export { isAvailable } from './lib/availability.js'; // --- Session management (for test cleanup) --- export { closeAll } from './lib/session-manager.js'; diff --git a/src/tui-harness/lib/availability.ts b/src/tui-harness/lib/availability.ts index ad1f2953b..c246d6004 100644 --- a/src/tui-harness/lib/availability.ts +++ b/src/tui-harness/lib/availability.ts @@ -21,20 +21,13 @@ const require = createRequire(import.meta.url); * Whether node-pty is available and its native addon loaded successfully. * * When `true`, it is safe to `import * as pty from 'node-pty'` and use - * the PTY APIs. When `false`, check {@link unavailableReason} for details. + * the PTY APIs. When `false`, node-pty could not be loaded. */ export let isAvailable = false; -/** - * Human-readable reason why node-pty is not available. - * - * Empty string when {@link isAvailable} is `true`. - */ -export let unavailableReason = ''; - try { require('node-pty'); isAvailable = true; -} catch (err) { - unavailableReason = `node-pty not available: ${(err as Error).message}`; +} catch { + // node-pty unavailable; isAvailable stays false } diff --git a/src/tui-harness/lib/screen.ts b/src/tui-harness/lib/screen.ts index 71e7a2616..acd60ba6b 100644 --- a/src/tui-harness/lib/screen.ts +++ b/src/tui-harness/lib/screen.ts @@ -86,28 +86,6 @@ export function getBufferType(terminal: Terminal): 'normal' | 'alternate' { return terminal.buffer.active.type; } -// --------------------------------------------------------------------------- -// Formatting -// --------------------------------------------------------------------------- - -/** - * Format an array of lines with right-aligned, 1-indexed line numbers. - * - * Example output for a 3-line array: - * ``` - * 1 | first line - * 2 | second line - * 3 | third line - * ``` - * - * @param lines - The lines to number. - * @returns A single string with newline-separated numbered lines. - */ -export function formatNumbered(lines: string[]): string { - const width = String(lines.length).length; - return lines.map((line, i) => `${String(i + 1).padStart(width)} | ${line}`).join('\n'); -} - // --------------------------------------------------------------------------- // Composite snapshot // --------------------------------------------------------------------------- diff --git a/src/tui-harness/lib/session-manager.ts b/src/tui-harness/lib/session-manager.ts index cddc1cafc..38a0fd6f5 100644 --- a/src/tui-harness/lib/session-manager.ts +++ b/src/tui-harness/lib/session-manager.ts @@ -107,25 +107,6 @@ export function unregister(sessionId: string): void { sessions.delete(sessionId); } -/** - * Look up a session by its unique identifier. - * - * @param sessionId - The session ID to look up. - * @returns The managed session, or undefined if no session with that ID is registered. - */ -export function get(sessionId: string): ManagedSession | undefined { - return sessions.get(sessionId); -} - -/** - * Return metadata for all currently registered sessions. - * - * @returns An array of {@link SessionInfo} objects, one per registered session. - */ -export function listAll(): SessionInfo[] { - return Array.from(sessions.values()).map(s => s.info); -} - /** * Close all registered sessions and clear the registry. *