Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/cli/commands/add/__tests__/add-identity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

describe('add identity command', () => {
describe('add credential command', () => {
let testDir: string;
let projectDir: string;

Expand All @@ -28,27 +28,27 @@ describe('add identity command', () => {

describe('validation', () => {
it('requires name flag', async () => {
const result = await runCLI(['add', 'identity', '--json'], projectDir);
const result = await runCLI(['add', 'credential', '--json'], projectDir);
expect(result.exitCode).toBe(1);
const json = JSON.parse(result.stdout);
expect(json.success).toBe(false);
expect(json.error.includes('--name'), `Error: ${json.error}`).toBeTruthy();
});

it('requires api-key flag', async () => {
const result = await runCLI(['add', 'identity', '--name', 'test', '--json'], projectDir);
const result = await runCLI(['add', 'credential', '--name', 'test', '--json'], projectDir);
expect(result.exitCode).toBe(1);
const json = JSON.parse(result.stdout);
expect(json.success).toBe(false);
expect(json.error.includes('--api-key'), `Error: ${json.error}`).toBeTruthy();
});
});

describe('identity creation', () => {
describe('credential creation', () => {
it('creates credential as top-level resource', async () => {
const identityName = `id${Date.now()}`;
const result = await runCLI(
['add', 'identity', '--name', identityName, '--api-key', 'test-key-123', '--json'],
['add', 'credential', '--name', identityName, '--api-key', 'test-key-123', '--json'],
projectDir
);

Expand All @@ -65,13 +65,13 @@ describe('add identity command', () => {
});
});

describe('oauth identity creation', () => {
describe('oauth credential creation', () => {
it('creates OAuth credential with discovery URL and scopes', async () => {
const identityName = `oauth-${Date.now()}`;
const result = await runCLI(
[
'add',
'identity',
'credential',
'--type',
'oauth',
'--name',
Expand Down
28 changes: 14 additions & 14 deletions src/cli/commands/add/__tests__/validate.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import type {
AddAgentOptions,
AddCredentialOptions,
AddGatewayOptions,
AddGatewayTargetOptions,
AddIdentityOptions,
AddMemoryOptions,
} from '../types.js';
import {
validateAddAgentOptions,
validateAddCredentialOptions,
validateAddGatewayOptions,
validateAddGatewayTargetOptions,
validateAddIdentityOptions,
validateAddMemoryOptions,
} from '../validate.js';
import { existsSync, readFileSync } from 'fs';
Expand Down Expand Up @@ -75,7 +75,7 @@ const validMemoryOptions: AddMemoryOptions = {
strategies: 'SEMANTIC,SUMMARIZATION',
};

const validIdentityOptions: AddIdentityOptions = {
const validCredentialOptions: AddCredentialOptions = {
name: 'test-identity',
apiKey: 'test-key',
};
Expand Down Expand Up @@ -996,25 +996,25 @@ describe('validate', () => {
});
});

describe('validateAddIdentityOptions', () => {
describe('validateAddCredentialOptions', () => {
// AC23: Required fields validated
it('returns error for missing required fields', () => {
const requiredFields: { field: keyof AddIdentityOptions; error: string }[] = [
const requiredFields: { field: keyof AddCredentialOptions; error: string }[] = [
{ field: 'name', error: '--name is required' },
{ field: 'apiKey', error: '--api-key is required' },
];

for (const { field, error } of requiredFields) {
const opts = { ...validIdentityOptions, [field]: undefined };
const result = validateAddIdentityOptions(opts);
const opts = { ...validCredentialOptions, [field]: undefined };
const result = validateAddCredentialOptions(opts);
expect(result.valid, `Should fail for missing ${String(field)}`).toBe(false);
expect(result.error).toBe(error);
}
});

// AC25: Valid options pass
it('passes for valid options', () => {
expect(validateAddIdentityOptions(validIdentityOptions)).toEqual({ valid: true });
expect(validateAddCredentialOptions(validCredentialOptions)).toEqual({ valid: true });
});
});

Expand Down Expand Up @@ -1193,9 +1193,9 @@ describe('validate', () => {
});
});

describe('validateAddIdentityOptions OAuth', () => {
describe('validateAddCredentialOptions OAuth', () => {
it('passes for valid OAuth identity', () => {
const result = validateAddIdentityOptions({
const result = validateAddCredentialOptions({
name: 'my-oauth',
type: 'oauth',
discoveryUrl: 'https://auth.example.com/.well-known/openid-configuration',
Expand All @@ -1206,7 +1206,7 @@ describe('validate', () => {
});

it('returns error for OAuth without discovery-url', () => {
const result = validateAddIdentityOptions({
const result = validateAddCredentialOptions({
name: 'my-oauth',
type: 'oauth',
clientId: 'client123',
Expand All @@ -1217,7 +1217,7 @@ describe('validate', () => {
});

it('returns error for OAuth without client-id', () => {
const result = validateAddIdentityOptions({
const result = validateAddCredentialOptions({
name: 'my-oauth',
type: 'oauth',
discoveryUrl: 'https://auth.example.com',
Expand All @@ -1228,7 +1228,7 @@ describe('validate', () => {
});

it('returns error for OAuth without client-secret', () => {
const result = validateAddIdentityOptions({
const result = validateAddCredentialOptions({
name: 'my-oauth',
type: 'oauth',
discoveryUrl: 'https://auth.example.com',
Expand All @@ -1239,7 +1239,7 @@ describe('validate', () => {
});

it('still requires api-key for default type', () => {
const result = validateAddIdentityOptions({ name: 'my-key' });
const result = validateAddCredentialOptions({ name: 'my-key' });
expect(result.valid).toBe(false);
expect(result.error).toContain('--api-key');
});
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/add/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function registerAdd(program: Command): Command {
);
});

// Subcommands (agent, memory, identity, gateway, gateway-target) are registered
// Subcommands (agent, memory, credential, gateway, gateway-target) are registered
// via primitive.registerCommands() in cli.ts

return addCmd;
Expand Down
9 changes: 6 additions & 3 deletions src/cli/commands/add/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ export interface AddMemoryResult {
error?: string;
}

// Identity types (v2: credential, no owner/user concept)
export interface AddIdentityOptions {
// Credential types (v2: credential, no owner/user concept)
export interface AddCredentialOptions {
name?: string;
type?: 'api-key' | 'oauth';
apiKey?: string;
Expand All @@ -130,7 +130,10 @@ export interface AddIdentityOptions {
json?: boolean;
}

export interface AddIdentityResult {
/** @deprecated Use AddCredentialOptions */
export type AddIdentityOptions = AddCredentialOptions;

export interface AddCredentialResult {
success: boolean;
credentialName?: string;
error?: string;
Expand Down
8 changes: 4 additions & 4 deletions src/cli/commands/add/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { validateVpcOptions } from '../shared/vpc-utils';
import { validateJwtAuthorizerOptions } from './auth-options';
import type {
AddAgentOptions,
AddCredentialOptions,
AddGatewayOptions,
AddGatewayTargetOptions,
AddIdentityOptions,
AddMemoryOptions,
} from './types';
import { existsSync, readFileSync } from 'fs';
Expand Down Expand Up @@ -50,7 +50,7 @@ async function validateCredentialExists(credentialName: string): Promise<Validat
if (availableCredentials.length === 0) {
return {
valid: false,
error: `Credential "${credentialName}" not found. No credentials are configured. Add credentials using 'agentcore add identity'.`,
error: `Credential "${credentialName}" not found. No credentials are configured. Add credentials using 'agentcore add credential'.`,
};
}
return {
Expand Down Expand Up @@ -680,8 +680,8 @@ export function validateAddMemoryOptions(options: AddMemoryOptions): ValidationR
return { valid: true };
}

// Identity validation (v2: credential resource, no owner)
export function validateAddIdentityOptions(options: AddIdentityOptions): ValidationResult {
// Credential validation (v2: credential resource, no owner)
export function validateAddCredentialOptions(options: AddCredentialOptions): ValidationResult {
if (!options.name) {
return { valid: false, error: '--name is required' };
}
Expand Down
10 changes: 7 additions & 3 deletions src/cli/commands/deploy/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ async function handleDeployCLI(options: DeployOptions): Promise<void> {
if (options.diff) {
console.log(`\n✓ Diff complete for '${result.targetName}' (stack: ${result.stackName})`);
} else if (options.plan) {
console.log(`\n✓ Plan complete for '${result.targetName}' (stack: ${result.stackName})`);
console.log(`\n✓ Dry run complete for '${result.targetName}' (stack: ${result.stackName})`);
console.log('\nRun `agentcore deploy` to deploy.');
} else {
console.log(`\n✓ Deployed to '${result.targetName}' (stack: ${result.stackName})`);
Expand Down Expand Up @@ -136,23 +136,27 @@ export const registerDeploy = (program: Command) => {
.option('-y, --yes', 'Auto-confirm prompts, read credentials from env [non-interactive]')
.option('-v, --verbose', 'Show resource-level deployment events [non-interactive]')
.option('--json', 'Output as JSON [non-interactive]')
.option('--plan', 'Preview deployment without deploying (dry-run) [non-interactive]')
.option('--dry-run', 'Preview deployment without deploying [non-interactive]')
.option('--plan', 'Preview deployment without deploying (alias for --dry-run) [non-interactive]')
.option('--diff', 'Show CDK diff without deploying [non-interactive]')
.action(
async (cliOptions: {
target?: string;
yes?: boolean;
verbose?: boolean;
json?: boolean;
dryRun?: boolean;
plan?: boolean;
diff?: boolean;
}) => {
try {
requireProject();
if (cliOptions.json || cliOptions.target || cliOptions.plan || cliOptions.yes || cliOptions.verbose) {
const isDryRun = cliOptions.dryRun ?? cliOptions.plan;
if (cliOptions.json || cliOptions.target || isDryRun || cliOptions.yes || cliOptions.verbose) {
// CLI mode - any flag triggers non-interactive mode
const options = {
...cliOptions,
plan: isDryRun,
target: cliOptions.target ?? 'default',
progress: !cliOptions.json,
};
Expand Down
14 changes: 7 additions & 7 deletions src/cli/commands/remove/__tests__/remove-identity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

describe('remove identity command', () => {
describe('remove credential command', () => {
let testDir: string;
let projectDir: string;
const identityName = 'TestIdentity';
Expand All @@ -24,7 +24,7 @@ describe('remove identity command', () => {

// Add identity as top-level credential
result = await runCLI(
['add', 'identity', '--name', identityName, '--api-key', 'test-key-123', '--json'],
['add', 'credential', '--name', identityName, '--api-key', 'test-key-123', '--json'],
projectDir
);
if (result.exitCode !== 0) {
Expand All @@ -38,15 +38,15 @@ describe('remove identity command', () => {

describe('validation', () => {
it('requires name flag', async () => {
const result = await runCLI(['remove', 'identity', '--json'], projectDir);
const result = await runCLI(['remove', 'credential', '--json'], projectDir);
expect(result.exitCode).toBe(1);
const json = JSON.parse(result.stdout);
expect(json.success).toBe(false);
expect(json.error.includes('--name'), `Error: ${json.error}`).toBeTruthy();
});

it('rejects non-existent identity', async () => {
const result = await runCLI(['remove', 'identity', '--name', 'nonexistent', '--json'], projectDir);
const result = await runCLI(['remove', 'credential', '--name', 'nonexistent', '--json'], projectDir);
expect(result.exitCode).toBe(1);
const json = JSON.parse(result.stdout);
expect(json.success).toBe(false);
Expand All @@ -58,9 +58,9 @@ describe('remove identity command', () => {
it('removes credential without dependents', async () => {
// Add a temp credential to remove
const tempId = `tempId${Date.now()}`;
await runCLI(['add', 'identity', '--name', tempId, '--api-key', 'temp-key', '--json'], projectDir);
await runCLI(['add', 'credential', '--name', tempId, '--api-key', 'temp-key', '--json'], projectDir);

const result = await runCLI(['remove', 'identity', '--name', tempId, '--json'], projectDir);
const result = await runCLI(['remove', 'credential', '--name', tempId, '--json'], projectDir);
expect(result.exitCode, `stdout: ${result.stdout}`).toBe(0);
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);
Expand All @@ -72,7 +72,7 @@ describe('remove identity command', () => {
});

it('removes the setup credential', async () => {
const result = await runCLI(['remove', 'identity', '--name', identityName, '--json'], projectDir);
const result = await runCLI(['remove', 'credential', '--name', identityName, '--json'], projectDir);
expect(result.exitCode, `stdout: ${result.stdout}`).toBe(0);
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/remove/__tests__/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('validateRemoveOptions', () => {
});

it('returns valid with no json and no name', () => {
expect(validateRemoveOptions({ resourceType: 'identity' })).toEqual({ valid: true });
expect(validateRemoveOptions({ resourceType: 'credential' })).toEqual({ valid: true });
});
});

Expand Down
12 changes: 7 additions & 5 deletions src/cli/commands/remove/command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,17 @@ export const registerRemove = (program: Command): Command => {
removeCommand
.command('all')
.description('Reset all agentcore schemas to empty state')
.option('--force', 'Skip confirmation prompts [non-interactive]')
.option('-y, --yes', 'Skip confirmation prompts [non-interactive]')
.option('--force', 'Skip confirmation prompts (alias for --yes) [non-interactive]')
.option('--dry-run', 'Show what would be reset without actually resetting [non-interactive]')
.option('--json', 'Output as JSON [non-interactive]')
.action(async (cliOptions: { force?: boolean; dryRun?: boolean; json?: boolean }) => {
.action(async (cliOptions: { yes?: boolean; force?: boolean; dryRun?: boolean; json?: boolean }) => {
try {
const skipConfirm = cliOptions.yes ?? cliOptions.force;
// Any flag triggers non-interactive CLI mode
if (cliOptions.force || cliOptions.dryRun || cliOptions.json) {
if (skipConfirm || cliOptions.dryRun || cliOptions.json) {
await handleRemoveAllCLI({
force: cliOptions.force,
force: skipConfirm,
dryRun: cliOptions.dryRun,
json: cliOptions.json,
});
Expand All @@ -95,7 +97,7 @@ export const registerRemove = (program: Command): Command => {
}
});

// Resource subcommands (agent, memory, identity, gateway, mcp-tool) are registered
// Resource subcommands (agent, memory, credential, gateway, mcp-tool) are registered
// via primitive.registerCommands() in cli.ts

// Catch-all for TUI fallback when no subcommand is specified.
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/remove/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export type ResourceType =
| 'gateway'
| 'gateway-target'
| 'memory'
| 'identity'
| 'credential'
| 'evaluator'
| 'online-eval'
| 'policy-engine'
Expand Down
2 changes: 1 addition & 1 deletion src/cli/logging/remove-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface RemoveLoggerOptions {
resourceType:
| 'agent'
| 'memory'
| 'identity'
| 'credential'
| 'gateway'
| 'gateway-target'
| 'evaluator'
Expand Down
Loading
Loading