diff --git a/integ-tests/add-remove-gateway.test.ts b/integ-tests/add-remove-gateway.test.ts index ba4753c9b..53323c839 100644 --- a/integ-tests/add-remove-gateway.test.ts +++ b/integ-tests/add-remove-gateway.test.ts @@ -1,9 +1,12 @@ import { createTestProject, runCLI } from '../src/test-utils/index.js'; import type { TestProject } from '../src/test-utils/index.js'; +import { createTelemetryHelper } from '../src/test-utils/telemetry-helper.js'; import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +const telemetry = createTelemetryHelper(); + async function readProjectConfig(projectPath: string) { return JSON.parse(await readFile(join(projectPath, 'agentcore/agentcore.json'), 'utf-8')); } @@ -19,6 +22,7 @@ describe('integration: add and remove gateway with external MCP server', () => { afterAll(async () => { await project.cleanup(); + telemetry.destroy(); }); describe('gateway lifecycle', () => { @@ -64,7 +68,9 @@ describe('integration: add and remove gateway with external MCP server', () => { }); it('removes the gateway target', async () => { - const result = await runCLI(['remove', 'gateway-target', '--name', targetName, '--json'], project.projectPath); + const result = await runCLI(['remove', 'gateway-target', '--name', targetName, '--json'], project.projectPath, { + env: telemetry.env, + }); expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); const json = JSON.parse(result.stdout); @@ -75,10 +81,13 @@ describe('integration: add and remove gateway with external MCP server', () => { const targets = gateway?.targets ?? []; const found = targets.find((t: { name: string }) => t.name === targetName); expect(found, `Target "${targetName}" should be removed`).toBeFalsy(); + telemetry.assertMetricEmitted({ command: 'remove.gateway-target', exit_reason: 'success' }); }); it('removes the gateway', async () => { - const result = await runCLI(['remove', 'gateway', '--name', gatewayName, '--json'], project.projectPath); + const result = await runCLI(['remove', 'gateway', '--name', gatewayName, '--json'], project.projectPath, { + env: telemetry.env, + }); expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); const json = JSON.parse(result.stdout); @@ -88,6 +97,7 @@ describe('integration: add and remove gateway with external MCP server', () => { const gateways = mcpSpec.agentCoreGateways ?? []; const found = gateways.find((g: { name: string }) => g.name === gatewayName); expect(found, `Gateway "${gatewayName}" should be removed`).toBeFalsy(); + telemetry.assertMetricEmitted({ command: 'remove.gateway', exit_reason: 'success' }); }); }); }); diff --git a/integ-tests/add-remove-resources.test.ts b/integ-tests/add-remove-resources.test.ts index a89c761dd..72de14102 100644 --- a/integ-tests/add-remove-resources.test.ts +++ b/integ-tests/add-remove-resources.test.ts @@ -41,7 +41,6 @@ describe('integration: add and remove resources', () => { const found = memories!.some((m: Record) => m.name === memoryName); expect(found, `Memory "${memoryName}" should be in config`).toBe(true); - // Verify telemetry telemetry.assertMetricEmitted({ command: 'add.memory', exit_reason: 'success' }); }); @@ -71,7 +70,6 @@ describe('integration: add and remove resources', () => { expect(episodic!.reflectionNamespaces, 'Should have reflectionNamespaces').toBeDefined(); expect(episodic!.reflectionNamespaces!.length).toBeGreaterThan(0); - // Verify telemetry telemetry.assertMetricEmitted({ command: 'add.memory', exit_reason: 'success', @@ -84,7 +82,9 @@ describe('integration: add and remove resources', () => { }); it('removes the memory resource', async () => { - const result = await runCLI(['remove', 'memory', '--name', memoryName, '--json'], project.projectPath); + const result = await runCLI(['remove', 'memory', '--name', memoryName, '--json'], project.projectPath, { + env: telemetry.env, + }); expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); const json = JSON.parse(result.stdout); @@ -95,6 +95,8 @@ describe('integration: add and remove resources', () => { const memories = (config.memories as Record[] | undefined) ?? []; const found = memories.some((m: Record) => m.name === memoryName); expect(found, `Memory "${memoryName}" should be removed from config`).toBe(false); + + telemetry.assertMetricEmitted({ command: 'remove.memory', exit_reason: 'success' }); }); }); @@ -119,7 +121,6 @@ describe('integration: add and remove resources', () => { const found = credentials!.some((c: Record) => c.name === credentialName); expect(found, `Credential "${credentialName}" should be in config`).toBe(true); - // Verify telemetry telemetry.assertMetricEmitted({ command: 'add.credential', exit_reason: 'success', @@ -128,7 +129,9 @@ describe('integration: add and remove resources', () => { }); it('removes the credential resource', async () => { - const result = await runCLI(['remove', 'credential', '--name', credentialName, '--json'], project.projectPath); + const result = await runCLI(['remove', 'credential', '--name', credentialName, '--json'], project.projectPath, { + env: telemetry.env, + }); expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); const json = JSON.parse(result.stdout); @@ -139,6 +142,8 @@ describe('integration: add and remove resources', () => { const credentials = (config.credentials as Record[] | undefined) ?? []; const found = credentials.some((c: Record) => c.name === credentialName); expect(found, `Credential "${credentialName}" should be removed from config`).toBe(false); + + telemetry.assertMetricEmitted({ command: 'remove.credential', exit_reason: 'success' }); }); }); @@ -162,9 +167,37 @@ describe('integration: add and remove resources', () => { }); it('removes the policy engine resource', async () => { - const result = await runCLI(['remove', 'policy-engine', '--name', engineName, '--json'], project.projectPath); + const result = await runCLI(['remove', 'policy-engine', '--name', engineName, '--json'], project.projectPath, { + env: telemetry.env, + }); expect(result.exitCode, `stdout: ${result.stdout}, stderr: ${result.stderr}`).toBe(0); + + telemetry.assertMetricEmitted({ command: 'remove.policy-engine', exit_reason: 'success' }); + }); + }); + + describe('remove failure telemetry', () => { + it('emits failure telemetry for non-existent resource', async () => { + const result = await runCLI(['remove', 'memory', '--name', 'DoesNotExist', '--json'], project.projectPath, { + env: telemetry.env, + }); + + expect(result.exitCode).toBe(1); + telemetry.assertMetricEmitted({ command: 'remove.memory', exit_reason: 'failure' }); + }); + }); + + describe('remove all', () => { + it('resets all schemas and emits telemetry', async () => { + const result = await runCLI(['remove', 'all', '--yes', '--json'], project.projectPath, { + env: telemetry.env, + }); + + expect(result.exitCode).toBe(0); + const json = JSON.parse(result.stdout); + expect(json.success).toBe(true); + telemetry.assertMetricEmitted({ command: 'remove.all', exit_reason: 'success' }); }); }); }); diff --git a/src/cli/commands/remove/command.tsx b/src/cli/commands/remove/command.tsx index e978e9793..9c3d40426 100644 --- a/src/cli/commands/remove/command.tsx +++ b/src/cli/commands/remove/command.tsx @@ -1,5 +1,6 @@ import { ConfigIO } from '../../../lib'; import { getErrorMessage } from '../../errors'; +import { cliCommandRun } from '../../telemetry/cli-command-run.js'; import { COMMAND_DESCRIPTIONS } from '../../tui/copy'; import { requireProject, requireTTY } from '../../tui/guards'; import { RemoveAllScreen, RemoveFlow } from '../../tui/screens/remove'; @@ -51,9 +52,12 @@ async function handleRemoveAll(_options: RemoveAllOptions): Promise { validateRemoveAllOptions(options); - const result = await handleRemoveAll(options); - console.log(JSON.stringify(result)); - process.exit(result.success ? 0 : 1); + await cliCommandRun('remove.all', !!options.json, async () => { + const result = await handleRemoveAll(options); + if (!result.success) throw new Error(result.error); + console.log(JSON.stringify(result)); + return {}; + }); } export const registerRemove = (program: Command): Command => { diff --git a/src/cli/primitives/BasePrimitive.ts b/src/cli/primitives/BasePrimitive.ts index 149611c4f..11d10faee 100644 --- a/src/cli/primitives/BasePrimitive.ts +++ b/src/cli/primitives/BasePrimitive.ts @@ -2,6 +2,8 @@ import { ConfigIO, findConfigRoot } from '../../lib'; import type { AgentCoreProjectSpec } from '../../schema'; import type { ResourceType } from '../commands/remove/types'; import { getErrorMessage } from '../errors'; +import { withTuiTelemetry } from '../telemetry/cli-command-run.js'; +import type { SubCommand } from '../telemetry/schemas/command-run.js'; import { requireTTY } from '../tui/guards/tty'; import { SOURCE_CODE_NOTE } from './constants'; import type { AddResult, AddScreenComponent, RemovableResource, RemovalPreview, RemovalResult } from './types'; @@ -120,7 +122,11 @@ export abstract class BasePrimitive< process.exit(1); } - const result = await this.remove(cliOptions.name); + const result = await withTuiTelemetry, RemovalResult>( + `remove.${this.kind}`, + {}, + () => this.remove(cliOptions.name!) + ); console.log( JSON.stringify({ success: result.success, diff --git a/src/cli/primitives/GatewayPrimitive.ts b/src/cli/primitives/GatewayPrimitive.ts index 625a3c4a5..d48368221 100644 --- a/src/cli/primitives/GatewayPrimitive.ts +++ b/src/cli/primitives/GatewayPrimitive.ts @@ -12,7 +12,7 @@ import type { AddGatewayOptions as CLIAddGatewayOptions } from '../commands/add/ import { validateAddGatewayOptions } from '../commands/add/validate'; import { getErrorMessage } from '../errors'; import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; -import { cliCommandRun } from '../telemetry/cli-command-run.js'; +import { cliCommandRun, withTuiTelemetry } from '../telemetry/cli-command-run.js'; import { AuthorizerType, PolicyEngineMode, standardize } from '../telemetry/schemas/common-shapes.js'; import { requireTTY } from '../tui/guards/tty'; import type { AddGatewayConfig } from '../tui/screens/mcp/types'; @@ -262,7 +262,7 @@ export class GatewayPrimitive extends BasePrimitive this.remove(cliOptions.name!)); console.log( JSON.stringify({ success: result.success, diff --git a/src/cli/primitives/GatewayTargetPrimitive.ts b/src/cli/primitives/GatewayTargetPrimitive.ts index e8a1da996..55e72f17a 100644 --- a/src/cli/primitives/GatewayTargetPrimitive.ts +++ b/src/cli/primitives/GatewayTargetPrimitive.ts @@ -14,7 +14,7 @@ import { validateAddGatewayTargetOptions } from '../commands/add/validate'; import { getErrorMessage } from '../errors'; import type { RemovableGatewayTarget } from '../operations/remove/remove-gateway-target'; import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; -import { cliCommandRun } from '../telemetry/cli-command-run.js'; +import { cliCommandRun, withTuiTelemetry } from '../telemetry/cli-command-run.js'; import { GATEWAY_TARGET_TYPE_MAP, GatewayTargetHost, @@ -510,7 +510,7 @@ export class GatewayTargetPrimitive extends BasePrimitive this.remove(cliOptions.name!)); console.log( JSON.stringify({ success: result.success, diff --git a/src/cli/primitives/PolicyEnginePrimitive.ts b/src/cli/primitives/PolicyEnginePrimitive.ts index bb9b314d8..a1ee83b33 100644 --- a/src/cli/primitives/PolicyEnginePrimitive.ts +++ b/src/cli/primitives/PolicyEnginePrimitive.ts @@ -3,7 +3,7 @@ import type { AgentCoreProjectSpec, PolicyEngine } from '../../schema'; import { PolicyEngineModeSchema, PolicyEngineSchema } from '../../schema'; import { getErrorMessage } from '../errors'; import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; -import { cliCommandRun } from '../telemetry/cli-command-run.js'; +import { cliCommandRun, withTuiTelemetry } from '../telemetry/cli-command-run.js'; import { AttachMode, standardize } from '../telemetry/schemas/common-shapes.js'; import { requireTTY } from '../tui/guards/tty'; import { BasePrimitive } from './BasePrimitive'; @@ -316,7 +316,7 @@ export class PolicyEnginePrimitive extends BasePrimitive this.remove(cliOptions.name!)); if (cliOptions.json) { console.log( JSON.stringify({ diff --git a/src/cli/primitives/PolicyPrimitive.ts b/src/cli/primitives/PolicyPrimitive.ts index beefe3008..d00d3f3f7 100644 --- a/src/cli/primitives/PolicyPrimitive.ts +++ b/src/cli/primitives/PolicyPrimitive.ts @@ -5,7 +5,7 @@ import { detectRegion } from '../aws'; import { getPolicyGeneration, startPolicyGeneration } from '../aws/policy-generation'; import { getErrorMessage } from '../errors'; import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; -import { cliCommandRun } from '../telemetry/cli-command-run.js'; +import { cliCommandRun, withTuiTelemetry } from '../telemetry/cli-command-run.js'; import { ValidationMode, standardize } from '../telemetry/schemas/common-shapes.js'; import { requireTTY } from '../tui/guards/tty'; import { BasePrimitive } from './BasePrimitive'; @@ -400,7 +400,7 @@ export class PolicyPrimitive extends BasePrimitive this.remove(removeKey)); if (cliOptions.json) { console.log( diff --git a/src/cli/primitives/RuntimeEndpointPrimitive.ts b/src/cli/primitives/RuntimeEndpointPrimitive.ts index e055fd17a..3d9109422 100644 --- a/src/cli/primitives/RuntimeEndpointPrimitive.ts +++ b/src/cli/primitives/RuntimeEndpointPrimitive.ts @@ -4,7 +4,7 @@ import { RuntimeEndpointSchema } from '../../schema'; import type { ResourceType } from '../commands/remove/types'; import { getErrorMessage } from '../errors'; import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types'; -import { cliCommandRun } from '../telemetry/cli-command-run.js'; +import { cliCommandRun, withTuiTelemetry } from '../telemetry/cli-command-run.js'; import { BasePrimitive } from './BasePrimitive'; import { SOURCE_CODE_NOTE } from './constants'; import type { AddResult, AddScreenComponent, RemovableResource } from './types'; @@ -296,7 +296,7 @@ export class RuntimeEndpointPrimitive extends BasePrimitive this.remove(cliOptions.name!)); console.log( JSON.stringify({ success: result.success, diff --git a/src/cli/telemetry/cli-command-run.ts b/src/cli/telemetry/cli-command-run.ts index 987f05730..f309c1038 100644 --- a/src/cli/telemetry/cli-command-run.ts +++ b/src/cli/telemetry/cli-command-run.ts @@ -1,8 +1,10 @@ import { getErrorMessage } from '../errors'; -import type { AddResult } from '../primitives/types.js'; import { TelemetryClientAccessor } from './client-accessor.js'; import type { Command, CommandAttrs } from './schemas/command-run.js'; +/** Standard result shape for operations wrapped by {@link withTuiTelemetry}. */ +export type OperationResult = { success: true } | { success: false; error: string }; + /** * Run a CLI command with telemetry, standardized error output, and process.exit. * The callback should throw on failure and return telemetry attrs on success. @@ -38,14 +40,21 @@ export async function cliCommandRun( } /** - * Wrap a primitive .add() call with telemetry — used by TUI paths. - * CLI paths use {@link cliCommandRun} instead. + * Wrap a TUI operation with telemetry. Works for any `{ success: boolean }` result type + * (add, remove, etc.). CLI paths use {@link cliCommandRun} instead. + * + * Attrs are only recorded on success — on failure, `withCommandRun` records the + * error classification instead. + * + * @param command Telemetry command key (e.g. 'add.memory', 'remove.agent') + * @param attrs Command-specific telemetry attributes (pass `{}` for NoAttrs commands) + * @param fn The operation to wrap */ -export async function withAddTelemetry>( +export async function withTuiTelemetry( command: C, attrs: CommandAttrs, - fn: () => Promise> -): Promise> { + fn: () => Promise +): Promise { let client; try { client = await TelemetryClientAccessor.get(); @@ -53,7 +62,7 @@ export async function withAddTelemetry | undefined; + let result: R | undefined; try { await client.withCommandRun(command, async () => { result = await fn(); @@ -64,7 +73,7 @@ export async function withAddTelemetry = z.infer<(typeof COMMAND_SCHEMAS)[C]>; +/** Extract the command group prefix from a dotted command key (e.g. 'add' from 'add.agent'). */ +type CommandGroup = { + [C in Command]: C extends `${infer G}.${string}` ? G : C; +}[Command]; + +/** + * Type-safe lookup of a subcommand under a command group. + * Produces a compile-time error if `${G}.${S}` is not a registered command. + * + * @example + * SubCommand<'remove', 'agent'> // → 'remove.agent' + * SubCommand<'add', 'memory'> // → 'add.memory' + * SubCommand<'remove', 'bogus'> // → never (compile error at call site) + */ +export type SubCommand = Extract; + /** Derive command_group from command key (e.g. 'add.agent' → 'add') */ export function deriveCommandGroup(command: Command): string { const dot = command.indexOf('.'); diff --git a/src/cli/tui/hooks/useCreateEvaluator.ts b/src/cli/tui/hooks/useCreateEvaluator.ts index f1cad666f..86cbcce1d 100644 --- a/src/cli/tui/hooks/useCreateEvaluator.ts +++ b/src/cli/tui/hooks/useCreateEvaluator.ts @@ -1,6 +1,6 @@ import type { EvaluatorConfig } from '../../../schema'; import { evaluatorPrimitive } from '../../primitives/registry'; -import { withAddTelemetry } from '../../telemetry/cli-command-run.js'; +import { withTuiTelemetry } from '../../telemetry/cli-command-run.js'; import { Level, standardize } from '../../telemetry/schemas/common-shapes.js'; import { useCallback, useEffect, useState } from 'react'; @@ -18,7 +18,7 @@ export function useCreateEvaluator() { const create = useCallback(async (config: CreateEvaluatorConfig) => { setStatus({ state: 'loading' }); try { - const addResult = await withAddTelemetry( + const addResult = await withTuiTelemetry( 'add.evaluator', { evaluator_type: config.config.codeBased ? 'code-based' : 'llm-as-a-judge', diff --git a/src/cli/tui/hooks/useCreateMcp.ts b/src/cli/tui/hooks/useCreateMcp.ts index ec91666d0..72b4f8389 100644 --- a/src/cli/tui/hooks/useCreateMcp.ts +++ b/src/cli/tui/hooks/useCreateMcp.ts @@ -4,7 +4,7 @@ import { gatewayTargetPrimitive, policyEnginePrimitive, } from '../../primitives/registry'; -import { withAddTelemetry } from '../../telemetry/cli-command-run.js'; +import { withTuiTelemetry } from '../../telemetry/cli-command-run.js'; import { AuthorizerType, PolicyEngineMode, standardize } from '../../telemetry/schemas/common-shapes.js'; import type { AddGatewayConfig } from '../screens/mcp/types'; import { useCallback, useEffect, useState } from 'react'; @@ -25,7 +25,7 @@ export function useCreateGateway() { const createGateway = useCallback(async (config: AddGatewayConfig) => { setStatus({ state: 'loading' }); try { - const addResult = await withAddTelemetry( + const addResult = await withTuiTelemetry( 'add.gateway', { authorizer_type: standardize(AuthorizerType, config.authorizerType ?? 'NONE'), diff --git a/src/cli/tui/hooks/useCreateMemory.ts b/src/cli/tui/hooks/useCreateMemory.ts index d4196582f..5e7b695f3 100644 --- a/src/cli/tui/hooks/useCreateMemory.ts +++ b/src/cli/tui/hooks/useCreateMemory.ts @@ -2,7 +2,7 @@ import { ConfigIO } from '../../../lib'; import type { Memory } from '../../../schema'; import { getAvailableAgents } from '../../operations/attach'; import { memoryPrimitive } from '../../primitives/registry'; -import { withAddTelemetry } from '../../telemetry/cli-command-run.js'; +import { withTuiTelemetry } from '../../telemetry/cli-command-run.js'; import { useCallback, useEffect, useState } from 'react'; interface CreateMemoryConfig { @@ -26,7 +26,7 @@ export function useCreateMemory() { try { const strategiesStr = config.strategies.map(s => s.type).join(','); const strategyList = strategiesStr ? strategiesStr.split(',').map(s => s.trim().toUpperCase()) : []; - const addResult = await withAddTelemetry( + const addResult = await withTuiTelemetry( 'add.memory', { strategy_count: strategyList.length, diff --git a/src/cli/tui/hooks/useCreateOnlineEval.ts b/src/cli/tui/hooks/useCreateOnlineEval.ts index 840b8ae5c..cd85d71ea 100644 --- a/src/cli/tui/hooks/useCreateOnlineEval.ts +++ b/src/cli/tui/hooks/useCreateOnlineEval.ts @@ -1,5 +1,5 @@ import { onlineEvalConfigPrimitive } from '../../primitives/registry'; -import { withAddTelemetry } from '../../telemetry/cli-command-run.js'; +import { withTuiTelemetry } from '../../telemetry/cli-command-run.js'; import { useCallback, useEffect, useState } from 'react'; interface CreateOnlineEvalConfig { @@ -18,7 +18,7 @@ export function useCreateOnlineEval() { const create = useCallback(async (config: CreateOnlineEvalConfig) => { setStatus({ state: 'loading' }); try { - const addResult = await withAddTelemetry( + const addResult = await withTuiTelemetry( 'add.online-eval', { evaluator_count: config.evaluators.length, diff --git a/src/cli/tui/hooks/useRemove.ts b/src/cli/tui/hooks/useRemove.ts index 8331d38e6..8ee04ce8c 100644 --- a/src/cli/tui/hooks/useRemove.ts +++ b/src/cli/tui/hooks/useRemove.ts @@ -17,6 +17,8 @@ import { policyPrimitive, runtimeEndpointPrimitive, } from '../../primitives/registry'; +import { withTuiTelemetry } from '../../telemetry/cli-command-run.js'; +import type { SubCommand } from '../../telemetry/schemas/command-run.js'; import { useCallback, useEffect, useRef, useState } from 'react'; // Re-export types for consumers @@ -72,7 +74,11 @@ function useRemoveResource( const remove = useCallback(async (id: TIdentifier, preview?: RemovalPreview): Promise => { setState({ isLoading: true, result: null }); - const result = await removeFnRef.current(id); + const result = await withTuiTelemetry, RemovalResult>( + `remove.${resourceTypeRef.current}`, + {}, + () => removeFnRef.current(id) + ); setState({ isLoading: false, result }); let logPath: string | undefined; diff --git a/src/cli/tui/screens/agent/useAddAgent.ts b/src/cli/tui/screens/agent/useAddAgent.ts index 273eff4aa..2d1f55cb2 100644 --- a/src/cli/tui/screens/agent/useAddAgent.ts +++ b/src/cli/tui/screens/agent/useAddAgent.ts @@ -11,7 +11,7 @@ import { executeImportAgent } from '../../../operations/agent/import'; import { buildAuthorizerConfigFromJwtConfig, createManagedOAuthCredential } from '../../../primitives/auth-utils'; import { computeDefaultCredentialEnvVarName } from '../../../primitives/credential-utils'; import { credentialPrimitive } from '../../../primitives/registry'; -import { withAddTelemetry } from '../../../telemetry/cli-command-run.js'; +import { withTuiTelemetry } from '../../../telemetry/cli-command-run.js'; import { AgentType as AgentTypeEnum, AuthorizerType as AuthorizerTypeEnum, @@ -147,7 +147,7 @@ export function useAddAgent() { const addAgent = useCallback(async (config: AddAgentConfig): Promise => { setIsLoading(true); try { - const result = await withAddTelemetry( + const result = await withTuiTelemetry( 'add.agent', { language: standardize(Language, config.language), diff --git a/src/cli/tui/screens/identity/useCreateIdentity.ts b/src/cli/tui/screens/identity/useCreateIdentity.ts index e214d1e67..fe8241aeb 100644 --- a/src/cli/tui/screens/identity/useCreateIdentity.ts +++ b/src/cli/tui/screens/identity/useCreateIdentity.ts @@ -2,7 +2,7 @@ import { ConfigIO } from '../../../../lib'; import type { Credential } from '../../../../schema'; import type { AddCredentialOptions } from '../../../primitives/CredentialPrimitive'; import { credentialPrimitive } from '../../../primitives/registry'; -import { withAddTelemetry } from '../../../telemetry/cli-command-run.js'; +import { withTuiTelemetry } from '../../../telemetry/cli-command-run.js'; import { useCallback, useEffect, useState } from 'react'; interface CreateStatus { @@ -17,7 +17,7 @@ export function useCreateIdentity() { const create = useCallback(async (config: AddCredentialOptions) => { setStatus({ state: 'loading' }); try { - const result = await withAddTelemetry( + const result = await withTuiTelemetry( 'add.credential', { credential_type: config.authorizerType === 'OAuthCredentialProvider' ? 'oauth' : 'api-key', diff --git a/src/cli/tui/screens/policy/AddPolicyFlow.tsx b/src/cli/tui/screens/policy/AddPolicyFlow.tsx index 9b3542cb8..e7ea47849 100644 --- a/src/cli/tui/screens/policy/AddPolicyFlow.tsx +++ b/src/cli/tui/screens/policy/AddPolicyFlow.tsx @@ -1,5 +1,5 @@ import { policyEnginePrimitive, policyPrimitive } from '../../../primitives/registry'; -import { withAddTelemetry } from '../../../telemetry/cli-command-run.js'; +import { withTuiTelemetry } from '../../../telemetry/cli-command-run.js'; import { AttachMode, ValidationMode, standardize } from '../../../telemetry/schemas/common-shapes.js'; import { ErrorPrompt, @@ -130,7 +130,7 @@ export function AddPolicyFlow({ isInteractive = true, onExit, onBack, onDev, onD }, []); const commitEngine = useCallback(async (engineName: string, gateways?: string[], mode?: 'LOG_ONLY' | 'ENFORCE') => { - const result = await withAddTelemetry( + const result = await withTuiTelemetry( 'add.policy-engine', { attach_gateway_count: gateways?.length ?? 0, @@ -164,7 +164,7 @@ export function AddPolicyFlow({ isInteractive = true, onExit, onBack, onDev, onD ); const handlePolicyComplete = useCallback(async (config: AddPolicyConfig) => { - const result = await withAddTelemetry( + const result = await withTuiTelemetry( 'add.policy', { source_type: config.sourceFile ? 'file' : config.sourceMethod === 'generate' ? 'generate' : 'statement', diff --git a/src/cli/tui/screens/remove/useRemoveFlow.ts b/src/cli/tui/screens/remove/useRemoveFlow.ts index 8c4d3ef69..ed7e0ddc7 100644 --- a/src/cli/tui/screens/remove/useRemoveFlow.ts +++ b/src/cli/tui/screens/remove/useRemoveFlow.ts @@ -1,7 +1,9 @@ import { ConfigIO, getWorkingDirectory } from '../../../../lib'; import { findStack } from '../../../cloudformation/stack-discovery'; import { getErrorMessage } from '../../../errors'; +import type { RemovalResult } from '../../../operations/remove/types'; import { createDefaultProjectSpec } from '../../../project'; +import { withTuiTelemetry } from '../../../telemetry/cli-command-run.js'; import { type Step, areStepsComplete, hasStepError } from '../../components'; import { withMinDuration } from '../../utils'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -146,16 +148,23 @@ export function useRemoveFlow({ force, dryRun }: RemoveFlowOptions): RemoveFlowS // Reset all schemas to default empty state updateStep(0, { status: 'running' }); try { - await withMinDuration(async () => { - const configIO = new ConfigIO(); - - // Reset agentcore.json (keep project name) - const defaultProjectSpec = createDefaultProjectSpec(projectName || 'Project'); - await configIO.writeProjectSpec(defaultProjectSpec); - - // Preserve aws-targets.json and deployed-state.json so that - // a subsequent `agentcore deploy` can tear down existing stacks. - }); + const res = await withTuiTelemetry( + 'remove.all', + {}, + (): Promise => + withMinDuration(async () => { + const configIO = new ConfigIO(); + + // Reset agentcore.json (keep project name) + const defaultProjectSpec = createDefaultProjectSpec(projectName || 'Project'); + await configIO.writeProjectSpec(defaultProjectSpec); + + // Preserve aws-targets.json and deployed-state.json so that + // a subsequent `agentcore deploy` can tear down existing stacks. + return { success: true }; + }) + ); + if (!res.success) throw new Error(res.error); updateStep(0, { status: 'success' }); } catch (err) { updateStep(0, { status: 'error', error: getErrorMessage(err) }); diff --git a/src/cli/tui/screens/runtime-endpoint/AddRuntimeEndpointFlow.tsx b/src/cli/tui/screens/runtime-endpoint/AddRuntimeEndpointFlow.tsx index 83bda78e5..72ee36cd1 100644 --- a/src/cli/tui/screens/runtime-endpoint/AddRuntimeEndpointFlow.tsx +++ b/src/cli/tui/screens/runtime-endpoint/AddRuntimeEndpointFlow.tsx @@ -1,6 +1,6 @@ import { ConfigIO } from '../../../../lib'; import { runtimeEndpointPrimitive } from '../../../primitives/registry'; -import { withAddTelemetry } from '../../../telemetry/cli-command-run.js'; +import { withTuiTelemetry } from '../../../telemetry/cli-command-run.js'; import { ErrorPrompt } from '../../components'; import { AddSuccessScreen } from '../add/AddSuccessScreen'; import { AddRuntimeEndpointScreen } from './AddRuntimeEndpointScreen'; @@ -79,7 +79,7 @@ export function AddRuntimeEndpointFlow({ }, [isInteractive, flow.name, onExit]); const handleCreateComplete = useCallback((config: RuntimeEndpointWizardConfig) => { - void withAddTelemetry('add.runtime-endpoint', {}, () => + void withTuiTelemetry('add.runtime-endpoint', {}, () => runtimeEndpointPrimitive.add({ runtime: config.runtimeName, endpoint: config.endpointName,