Skip to content
Open
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
3 changes: 3 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export default tseslint.config(
...importPlugin.configs.recommended.rules,
...react.configs.recommended.rules,
...security.configs.recommended.rules,
// CLI inherently works with dynamic file paths and Record lookups — these are false positives
'security/detect-non-literal-fs-filename': 'off',
'security/detect-object-injection': 'off',
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
'react-hooks/preserve-manual-memoization': 'warn',
Expand Down
5 changes: 2 additions & 3 deletions src/cli/cdk/local-cdk-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export class LocalCdkProject {
*/
exists(): boolean {
const packageJson = path.join(this.projectDir, 'package.json');
// eslint-disable-next-line security/detect-non-literal-fs-filename

return fs.existsSync(this.projectDir) && fs.existsSync(packageJson);
}

Expand All @@ -41,13 +41,12 @@ export class LocalCdkProject {
* Throws an error if the project is missing or invalid.
*/
validate(): void {
// eslint-disable-next-line security/detect-non-literal-fs-filename
if (!fs.existsSync(this.projectDir)) {
throw new Error(`CDK project not found at ${this.projectDir}. Run 'agentcore create' first.`);
}

const packageJson = path.join(this.projectDir, 'package.json');
// eslint-disable-next-line security/detect-non-literal-fs-filename

if (!fs.existsSync(packageJson)) {
throw new Error(`Invalid CDK project: missing package.json in ${this.projectDir}`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import { runCLI } from '../../../../test-utils/index.js';
import { randomUUID } from 'node:crypto';
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/create/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export async function createProject(options: CreateProjectOptions): Promise<Crea
try {
// Create project directory
onProgress?.(`Create ${name}/ project directory`, 'start');
// eslint-disable-next-line security/detect-non-literal-fs-filename

await mkdir(projectRoot, { recursive: true });
onProgress?.(`Create ${name}/ project directory`, 'done');

Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/create/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const MEMORY_OPTIONS = ['none', 'shortTerm', 'longAndShortTerm'] as const;
/** Check if a folder with the given name already exists in the directory */
export function validateFolderNotExists(name: string, cwd: string): true | string {
const projectPath = join(cwd, name);
// eslint-disable-next-line security/detect-non-literal-fs-filename

if (existsSync(projectPath)) {
return `A folder named '${name}' already exists in this directory`;
}
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/deploy/__tests__/deploy-teardown.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('deploy with empty agents and deployed state (teardown)', () => {
it('requires --yes to confirm teardown deploy when deployed state exists', async () => {
// Write aws-targets.json so deploy can find the target
const awsTargetsPath = join(projectDir, 'agentcore', 'aws-targets.json');
// eslint-disable-next-line security/detect-non-literal-fs-filename

await writeFile(
awsTargetsPath,
JSON.stringify([{ name: 'default', account: '123456789012', region: 'us-east-1' }])
Expand All @@ -60,7 +60,7 @@ describe('deploy with empty agents and deployed state (teardown)', () => {
const cliDir = join(projectDir, 'agentcore', '.cli');
await mkdir(cliDir, { recursive: true });
const deployedStatePath = join(cliDir, 'deployed-state.json');
// eslint-disable-next-line security/detect-non-literal-fs-filename

await writeFile(
deployedStatePath,
JSON.stringify({
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/deploy/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('deploy without agents', () => {

beforeAll(async () => {
noAgentTestDir = join(tmpdir(), `agentcore-deploy-noagent-${randomUUID()}`);
// eslint-disable-next-line security/detect-non-literal-fs-filename

await mkdir(noAgentTestDir, { recursive: true });

// Create project without any agents
Expand All @@ -41,7 +41,7 @@ describe('deploy without agents', () => {

// Write aws-targets.json directly (replaces old 'add target' command)
const awsTargetsPath = join(noAgentProjectDir, 'agentcore', 'aws-targets.json');
// eslint-disable-next-line security/detect-non-literal-fs-filename

await writeFile(
awsTargetsPath,
JSON.stringify([{ name: 'default', account: '123456789012', region: 'us-east-1' }])
Expand Down
14 changes: 7 additions & 7 deletions src/cli/commands/remove/__tests__/remove-all.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('remove all command', () => {

beforeAll(async () => {
testDir = join(tmpdir(), `agentcore-remove-all-${randomUUID()}`);
// eslint-disable-next-line security/detect-non-literal-fs-filename

await mkdir(testDir, { recursive: true });

// Create project with agent
Expand Down Expand Up @@ -53,7 +53,7 @@ describe('remove all command', () => {
it('preserves aws-targets.json and deployed-state.json after remove all', async () => {
// Write aws-targets.json so we can verify it's preserved
const awsTargetsPath = join(projectDir, 'agentcore', 'aws-targets.json');
// eslint-disable-next-line security/detect-non-literal-fs-filename

await writeFile(
awsTargetsPath,
JSON.stringify([{ name: 'default', account: '123456789012', region: 'us-east-1' }])
Expand All @@ -62,10 +62,10 @@ describe('remove all command', () => {
// Simulate a deployed state entry so we can verify it is preserved
// deployed-state.json lives in agentcore/.cli/
const cliDir = join(projectDir, 'agentcore', '.cli');
// eslint-disable-next-line security/detect-non-literal-fs-filename

await mkdir(cliDir, { recursive: true });
const deployedStatePath = join(cliDir, 'deployed-state.json');
// eslint-disable-next-line security/detect-non-literal-fs-filename

await writeFile(
deployedStatePath,
JSON.stringify({ targets: { default: { resources: { stackName: 'TestStack' } } } })
Expand All @@ -78,20 +78,20 @@ describe('remove all command', () => {
expect(json.success).toBe(true);

// Verify aws-targets.json is preserved (NOT reset to empty)
// eslint-disable-next-line security/detect-non-literal-fs-filename

const targetsAfter = JSON.parse(await readFile(awsTargetsPath, 'utf-8'));
expect(targetsAfter.length, 'aws-targets.json should be preserved after remove all').toBe(1);

// Verify deployed-state.json is preserved (NOT reset to empty)
// eslint-disable-next-line security/detect-non-literal-fs-filename

const deployedStateAfter = JSON.parse(await readFile(deployedStatePath, 'utf-8'));
expect(
Object.keys(deployedStateAfter.targets).length,
'deployed-state.json targets should be preserved after remove all'
).toBe(1);

// Verify agentcore.json agents ARE cleared
// eslint-disable-next-line security/detect-non-literal-fs-filename

const schema = JSON.parse(await readFile(join(projectDir, 'agentcore', 'agentcore.json'), 'utf-8'));
expect(schema.agents.length, 'Agents should be cleared after remove all').toBe(0);
});
Expand Down
1 change: 0 additions & 1 deletion src/cli/commands/validate/__tests__/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ describe('handleValidate', () => {
it('formats ConfigValidationError with its message', async () => {
mockFindConfigRoot.mockReturnValue('/project/agentcore');
const { ConfigValidationError } = await import('../../../../lib/index.js');
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
mockReadProjectSpec.mockRejectedValue(new (ConfigValidationError as any)('field "name" is required'));

const result = await handleValidate({});
Expand Down
1 change: 0 additions & 1 deletion src/cli/logging/remove-logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable security/detect-object-injection, security/detect-non-literal-fs-filename */
import { CLI_LOGS_DIR, CLI_SYSTEM_DIR, CONFIG_DIR, findConfigRoot } from '../../lib';
import type { RemovalPreview } from '../operations/remove';
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import type { GenerateConfig } from '../../../../tui/screens/generate/types.js';
import type { CredentialStrategy } from '../../../identity/create-identity.js';
import { randomUUID } from 'node:crypto';
Expand Down
2 changes: 0 additions & 2 deletions src/cli/operations/mcp/__tests__/create-mcp-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ describe('createGatewayFromWizard', () => {
} as Parameters<typeof createGatewayFromWizard>[0]);

expect(result.name).toBe('new-gw');
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(mockWriteMcpSpec.mock.calls[0]![0].agentCoreGateways).toHaveLength(2);
});

Expand Down Expand Up @@ -237,7 +236,6 @@ describe('createGatewayFromWizard', () => {
},
} as Parameters<typeof createGatewayFromWizard>[0]);

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
expect(mockWriteMcpSpec.mock.calls[0]![0].agentCoreGateways[0].authorizerConfiguration).toEqual({
customJwtAuthorizer: {
discoveryUrl: 'https://example.com/.well-known/openid',
Expand Down
2 changes: 2 additions & 0 deletions src/cli/tui/components/StepProgress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ export interface Step {
info?: string;
}

// eslint-disable-next-line react-refresh/only-export-components
export function hasStepError(steps: Step[]): boolean {
return steps.some(s => s.status === 'error');
}

// eslint-disable-next-line react-refresh/only-export-components
export function areStepsComplete(steps: Step[]): boolean {
if (steps.length === 0) return false;
return steps.every(s => s.status === 'success' || s.status === 'error' || s.status === 'warn' || s.status === 'info');
Expand Down
2 changes: 2 additions & 0 deletions src/cli/tui/context/LayoutContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const LayoutContext = createContext<LayoutContextValue>({
contentWidth: MAX_CONTENT_WIDTH,
});

// eslint-disable-next-line react-refresh/only-export-components
export function useLayout(): LayoutContextValue {
return useContext(LayoutContext);
}
Expand All @@ -22,6 +23,7 @@ export function useLayout(): LayoutContextValue {
* The logo has fixed text " >_ AgentCore" on left and version on right,
* with padding in between to fill the width.
*/
// eslint-disable-next-line react-refresh/only-export-components
export function buildLogo(width: number, version?: string): string {
const left = '│ >_ AgentCore';
const right = version ? `v${version} │` : '│';
Expand Down
3 changes: 3 additions & 0 deletions src/cli/tui/guards/project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import React from 'react';
* Check if the agentcore/ project directory exists.
* Walks up from baseDir to find the agentcore directory.
*/
// eslint-disable-next-line react-refresh/only-export-components
export function projectExists(baseDir: string = getWorkingDirectory()): boolean {
return findConfigRoot(baseDir) !== null;
}
Expand All @@ -17,6 +18,7 @@ export function projectExists(baseDir: string = getWorkingDirectory()): boolean
* Returns the project root path if cwd is a subdirectory, or null if at the root.
* Returns null if no project is found at all (use projectExists for that check).
*/
// eslint-disable-next-line react-refresh/only-export-components
export function getProjectRootMismatch(baseDir: string = getWorkingDirectory()): string | null {
const configRoot = findConfigRoot(baseDir);
if (!configRoot) {
Expand Down Expand Up @@ -78,6 +80,7 @@ export function WrongDirectoryMessage({ projectRoot }: { projectRoot: string })
*
* @param inTui - If true, shows "create" instead of "agentcore create"
*/
// eslint-disable-next-line react-refresh/only-export-components
export function requireProject(inTui = false): void {
const cwd = getWorkingDirectory();
const configRoot = findConfigRoot(cwd);
Expand Down
1 change: 1 addition & 0 deletions src/cli/tui/hooks/useDevServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ export function useDevServer(options: { workingDir: string; port: number; agentN
config?.module,
config?.directory,
config?.isPython,
options.workingDir,
targetPort,
restartTrigger,
envVars,
Expand Down
1 change: 1 addition & 0 deletions src/cli/tui/hooks/useProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface UseProjectResult {
* }
*/
export function useProject(): UseProjectResult {
// eslint-disable-next-line react-hooks/preserve-manual-memoization -- intentionally empty deps; findConfigRoot() result is stable for the process lifetime
return useMemo(() => {
const configRoot = findConfigRoot();

Expand Down
1 change: 1 addition & 0 deletions src/cli/tui/screens/add/AddSuccessScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { ReactNode } from 'react';
import React from 'react';

/** Next steps shown after successfully adding a resource */
// eslint-disable-next-line react-refresh/only-export-components
export function getAddSuccessSteps(showDevOption: boolean): NextStep[] {
if (showDevOption) {
return [
Expand Down
1 change: 1 addition & 0 deletions src/cli/tui/screens/deploy/useDeployFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ export function useDeployFlow(options: DeployFlowOptions = {}): DeployFlowState
persistDeployedState,
switchableIoHost,
context?.isTeardownDeploy,
context?.awsTargets,
]);

// Finalize logger and dispose toolkit when preflight fails
Expand Down
1 change: 1 addition & 0 deletions src/cli/tui/screens/generate/GenerateWizardUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export function GenerateWizardUI({
/**
* Returns the appropriate help text for the current wizard step.
*/
// eslint-disable-next-line react-refresh/only-export-components
export function getWizardHelpText(step: GenerateStep): string {
if (step === 'confirm') return 'Enter/Y confirm · Esc back';
if (step === 'projectName') return 'Enter submit · Esc cancel';
Expand Down
1 change: 0 additions & 1 deletion src/lib/packaging/__tests__/helpers.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import {
MAX_ZIP_SIZE_BYTES,
convertWindowsScriptsToLinux,
Expand Down
1 change: 1 addition & 0 deletions src/lib/packaging/python.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { ArtifactResult, CodeZipPackager, PackageOptions, RuntimePackager }
import { detectUnavailablePlatform } from './uv';
import { join } from 'path';

// eslint-disable-next-line security/detect-unsafe-regex -- bounded input from RuntimeVersion enum, not user input
const PYTHON_RUNTIME_REGEX = /PYTHON_(\d+)_?(\d+)?/;

/**
Expand Down
1 change: 0 additions & 1 deletion src/lib/schemas/io/__tests__/config-io.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import { ConfigNotFoundError, ConfigParseError, ConfigValidationError } from '../../../errors/config.js';
import { ConfigIO } from '../config-io.js';
import { NoProjectError } from '../path-resolver.js';
Expand Down
1 change: 0 additions & 1 deletion src/lib/utils/__tests__/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ describe('SecureCredentials', () => {

it('Node.js inspect is safe', () => {
const creds = new SecureCredentials({ SECRET: 'mypassword' });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const inspectFn = (creds as any)[Symbol.for('nodejs.util.inspect.custom')] as () => string;
const str = inspectFn.call(creds);

Expand Down
7 changes: 6 additions & 1 deletion src/schema/schemas/agent-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ export const GatewayNameSchema = z
.string()
.min(1)
.max(100)
.regex(/^([0-9a-zA-Z][-]?){1,100}$/, 'Gateway name must be alphanumeric with optional hyphens (max 100 chars)');
.regex(
// eslint-disable-next-line security/detect-unsafe-regex -- input bounded to 100 chars by .max(100) above
/^[0-9a-zA-Z](?:[0-9a-zA-Z-]*[0-9a-zA-Z])?$/,
'Gateway name must be alphanumeric with optional hyphens (max 100 chars)'
);

// ============================================================================
// Common Types
Expand Down Expand Up @@ -73,6 +77,7 @@ export const EntrypointSchema = z
.string()
.min(1)
.regex(
// eslint-disable-next-line security/detect-unsafe-regex -- character class quantifiers don't cause backtracking
/^[a-zA-Z0-9_][a-zA-Z0-9_/.-]*\.(py|ts|js)(:[a-zA-Z_][a-zA-Z0-9_]*)?$/,
'Must be a Python (.py) or TypeScript (.ts/.js) file path with optional handler (e.g., "main.py:handler" or "index.ts")'
) as unknown as z.ZodType<FilePath>;
Expand Down
1 change: 1 addition & 0 deletions src/schema/schemas/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ const PythonEntrypointSchema = z
.string()
.min(1)
.regex(
// eslint-disable-next-line security/detect-unsafe-regex -- character class quantifiers don't cause backtracking
/^[a-zA-Z0-9_][a-zA-Z0-9_/.-]*\.py(:[a-zA-Z_][a-zA-Z0-9_]*)?$/,
'Must be a Python file path with optional handler (e.g., "main.py:agent" or "src/handler.py:app")'
) as unknown as z.ZodType<FilePath>;
Expand Down
1 change: 0 additions & 1 deletion src/test-utils/config-reader.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';

Expand Down
1 change: 0 additions & 1 deletion src/test-utils/project-factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable security/detect-non-literal-fs-filename */
import { runCLI } from './cli-runner.js';
import { randomUUID } from 'node:crypto';
import { mkdir, rm } from 'node:fs/promises';
Expand Down
Loading