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
50 changes: 40 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ It gives you two ways to work, from the same binary:
- **A scriptable CLI** — every operation is a flag-driven subcommand that emits
JSON (`--json`), so it can be used by codeing agents and can drop cleanly into
scripts, CI, and automation.
- **An interactive TUI** — bare Harness and Runtime branches and leaves open
their corresponding menus and selection flows.
- **An interactive TUI** — bare Harness, Runtime, and Identity branches and
leaves open their corresponding menus and selection flows.

```bash
agentcore # launch the interactive TUI
Expand All @@ -26,8 +26,8 @@ responses. `agentcore` wraps all of that behind one ergonomic tool.

## Command surface

Commands with operation flags run headlessly. Bare Harness and Runtime branches
and leaves open their interactive flows.
Commands with operation flags run headlessly. Bare Harness, Runtime, and
Identity branches and leaves open their interactive flows.

```
agentcore # interactive TUI
Expand All @@ -49,12 +49,18 @@ agentcore # interactive TUI
│ ├── update
│ └── delete
├── identity # manage AgentCore Identity resources
│ └── api-key-credential-provider
│ ├── create # create an API key credential provider
│ ├── get # get an API key credential provider
│ ├── list # list API key credential providers
│ ├── update # update an API key credential provider
│ └── delete # delete an API key credential provider
│ ├── api-key-credential-provider
│ │ ├── create # create an API key credential provider
│ │ ├── get # get an API key credential provider
│ │ ├── list # list API key credential providers
│ │ ├── update # update an API key credential provider
│ │ └── delete # delete an API key credential provider
│ └── oauth2-credential-provider
│ ├── create # create an OAuth2 credential provider
│ ├── get # get an OAuth2 credential provider
│ ├── list # list OAuth2 credential providers
│ ├── update # update an OAuth2 credential provider
│ └── delete # delete an OAuth2 credential provider
├── runtime # inspect deployed AgentCore Runtimes
│ ├── get # fetch a Runtime by id
│ ├── list # list Runtimes (server-side paginated)
Expand Down Expand Up @@ -152,6 +158,17 @@ agentcore identity api-key-credential-provider list --max-results 10
agentcore identity api-key-credential-provider update --name my-provider --api-key <new-key>
agentcore identity api-key-credential-provider delete --name my-provider

# Manage OAuth2 credential providers (guided Custom OAuth2, or --provider-configuration for other vendors)
agentcore identity oauth2-credential-provider create \
--name my-oauth-provider \
--vendor CustomOauth2 \
--client-id <client-id> \
--discovery-url https://issuer.example.com/.well-known/openid-configuration \
--client-secret -
agentcore identity oauth2-credential-provider get --name my-oauth-provider
agentcore identity oauth2-credential-provider list --max-results 10
agentcore identity oauth2-credential-provider delete --name my-oauth-provider

# Manage evaluators
# Create an LLM-as-a-Judge evaluator with a rating-scale preset.
agentcore eval evaluator llm-as-a-judge create \
Expand Down Expand Up @@ -271,6 +288,19 @@ agentcore runtime version list
agentcore runtime endpoint list
```

The Identity TUI is read-only: bare `identity` branches and the `get`/`list`
leaves open interactive menus and detail views. Mutations (`create`, `update`,
`delete`) are intentionally CLI-only — selecting one in the TUI points you at the
equivalent CLI command rather than hosting a write flow.

```bash
agentcore identity
agentcore identity api-key-credential-provider list
agentcore identity api-key-credential-provider get
agentcore identity oauth2-credential-provider list
agentcore identity oauth2-credential-provider get
```

---

# Architecture & patterns
Expand Down
39 changes: 39 additions & 0 deletions src/components/CliOnlyScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Box, Text, useInput } from "ink";
import { useNavigate } from "react-router";
import { Layout } from "./Layout";
import { darkTheme } from "./ui/_core.js";
import type { ScreenProps } from "../handlers/types";

export interface CliOnlyScreenProps extends ScreenProps {
breadcrumb: string[];
// command is the CLI invocation shown to the user
// operation, e.g. "agentcore identity api-key-credential-provider create --help".
command: string;
}

// CliOnlyScreen is the supported TUI experience for operations that are
// intentionally CLI-only
export function CliOnlyScreen({ breadcrumb, command }: CliOnlyScreenProps) {
const navigate = useNavigate();

useInput((_input, key) => {
if (key.escape) navigate(-1);
});

return (
<Layout
breadcrumb={breadcrumb}
keyHints={[
{ key: "esc", label: "back" },
{ key: "ctl+c", label: "quit" },
]}
>
<Box flexDirection="column" paddingLeft={1}>
<Text color={darkTheme.colors.text}>This operation is available via the CLI:</Text>
<Box paddingTop={1}>
<Text color={darkTheme.colors.focus}>{command}</Text>
</Box>
</Box>
</Layout>
);
}
4 changes: 3 additions & 1 deletion src/components/PaginatedTablePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface PaginatedTablePickerProps<TItem, TRow extends Record<string, un
errorMessage: (error: Error) => string;
emptyMessage: string;
emptyPageMessage: string;
maxPageSize?: number;
}

export function PaginatedTablePicker<TItem, TRow extends Record<string, unknown>>({
Expand All @@ -43,8 +44,9 @@ export function PaginatedTablePicker<TItem, TRow extends Record<string, unknown>
errorMessage,
emptyMessage,
emptyPageMessage,
maxPageSize,
}: PaginatedTablePickerProps<TItem, TRow>) {
const paging = usePagedList();
const paging = usePagedList(maxPageSize);
const list = useQuery({
queryKey: [...queryKey, paging.pageSize, paging.token],
queryFn: () => loadPage(paging.token, paging.pageSize),
Expand Down
85 changes: 85 additions & 0 deletions src/components/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ import { RuntimeListEndpointsScreen } from "../handlers/runtime/endpoint/list/sc
import { RuntimeVersionScreen } from "../handlers/runtime/version/screen.tsx";
import { RuntimeGetVersionScreen } from "../handlers/runtime/version/get/screen.tsx";
import { RuntimeListVersionsScreen } from "../handlers/runtime/version/list/screen.tsx";
import { IdentityScreen } from "../handlers/identity/screen.tsx";
import { ApiKeyCredentialProviderScreen } from "../handlers/identity/api-key-credential-provider/screen.tsx";
import { ApiKeyCredentialProviderListScreen } from "../handlers/identity/api-key-credential-provider/list/screen.tsx";
import {
ApiKeyCredentialProviderGetScreen,
ApiKeyCredentialProviderGetJsonScreen,
} from "../handlers/identity/api-key-credential-provider/get/screen.tsx";
import { ApiKeyCredentialProviderCreateScreen } from "../handlers/identity/api-key-credential-provider/create/screen.tsx";
import { ApiKeyCredentialProviderUpdateScreen } from "../handlers/identity/api-key-credential-provider/update/screen.tsx";
import { ApiKeyCredentialProviderDeleteScreen } from "../handlers/identity/api-key-credential-provider/delete/screen.tsx";
import { Oauth2CredentialProviderScreen } from "../handlers/identity/oauth2-credential-provider/screen.tsx";
import { Oauth2CredentialProviderListScreen } from "../handlers/identity/oauth2-credential-provider/list/screen.tsx";
import {
Oauth2CredentialProviderGetScreen,
Oauth2CredentialProviderGetJsonScreen,
} from "../handlers/identity/oauth2-credential-provider/get/screen.tsx";
import { Oauth2CredentialProviderCreateScreen } from "../handlers/identity/oauth2-credential-provider/create/screen.tsx";
import { Oauth2CredentialProviderUpdateScreen } from "../handlers/identity/oauth2-credential-provider/update/screen.tsx";
import { Oauth2CredentialProviderDeleteScreen } from "../handlers/identity/oauth2-credential-provider/delete/screen.tsx";
import { RootScreen, HelpScreen } from "../handlers/screen.tsx";
import type { Context } from "../router";

Expand Down Expand Up @@ -255,6 +274,72 @@ export function Root({ path, ctx, core, queryClient }: RootProps) {
path="agentcore/runtime/endpoint/list/:runtimeId"
element={<RuntimeListEndpointsScreen ctx={ctx} core={core} />}
/>
<Route path="agentcore/identity" element={<IdentityScreen ctx={ctx} core={core} />} />
<Route
path="agentcore/identity/api-key-credential-provider"
element={<ApiKeyCredentialProviderScreen ctx={ctx} core={core} />}
/>
{/* Bare `get` (no name) has nothing to show — send the user to the list. */}
<Route
path="agentcore/identity/api-key-credential-provider/get"
element={<Navigate to="/agentcore/identity/api-key-credential-provider/list" replace />}
/>
<Route
path="agentcore/identity/api-key-credential-provider/list"
element={<ApiKeyCredentialProviderListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/api-key-credential-provider/get/:name"
element={<ApiKeyCredentialProviderGetScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/api-key-credential-provider/get/:name/json"
element={<ApiKeyCredentialProviderGetJsonScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/api-key-credential-provider/create"
element={<ApiKeyCredentialProviderCreateScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/api-key-credential-provider/update"
element={<ApiKeyCredentialProviderUpdateScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/api-key-credential-provider/delete"
element={<ApiKeyCredentialProviderDeleteScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/oauth2-credential-provider"
element={<Oauth2CredentialProviderScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/oauth2-credential-provider/get"
element={<Navigate to="/agentcore/identity/oauth2-credential-provider/list" replace />}
/>
<Route
path="agentcore/identity/oauth2-credential-provider/list"
element={<Oauth2CredentialProviderListScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/oauth2-credential-provider/get/:name"
element={<Oauth2CredentialProviderGetScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/oauth2-credential-provider/get/:name/json"
element={<Oauth2CredentialProviderGetJsonScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/oauth2-credential-provider/create"
element={<Oauth2CredentialProviderCreateScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/oauth2-credential-provider/update"
element={<Oauth2CredentialProviderUpdateScreen ctx={ctx} core={core} />}
/>
<Route
path="agentcore/identity/oauth2-credential-provider/delete"
element={<Oauth2CredentialProviderDeleteScreen ctx={ctx} core={core} />}
/>
<Route path="*" element={<HelpScreen ctx={ctx} core={core} />} />
</Routes>
</MemoryRouter>
Expand Down
8 changes: 6 additions & 2 deletions src/components/usePagedList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ function initialPagination(pageSize: number): PaginationState {
// usePagedList holds the server-side pagination state shared by the picker
// tables: a terminal-height-derived page size and the trail of nextTokens
// leading to the current page, so ←/h can walk back through cached pages.
export function usePagedList(): PagedList {
//
// maxPageSize caps the terminal-derived page size for APIs that constrain
// maxResults (e.g. identity list operation capped at 20)
export function usePagedList(maxPageSize?: number): PagedList {
const { rows } = useWindowSize();
const pageSize = Math.max(3, rows - CHROME_ROWS);
const fitsTerminal = Math.max(3, rows - CHROME_ROWS);
const pageSize = maxPageSize ? Math.min(fitsTerminal, maxPageSize) : fitsTerminal;

const [state, setState] = useState<PaginationState>(() => initialPagination(pageSize));
const pageSizeChanged = state.pageSize !== pageSize;
Expand Down
58 changes: 58 additions & 0 deletions src/core/identity.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import {
CreateApiKeyCredentialProviderCommand,
CreateOauth2CredentialProviderCommand,
DeleteApiKeyCredentialProviderCommand,
DeleteOauth2CredentialProviderCommand,
GetApiKeyCredentialProviderCommand,
GetOauth2CredentialProviderCommand,
ListApiKeyCredentialProvidersCommand,
ListOauth2CredentialProvidersCommand,
UpdateApiKeyCredentialProviderCommand,
UpdateOauth2CredentialProviderCommand,
type CreateApiKeyCredentialProviderResponse,
type CreateOauth2CredentialProviderResponse,
type DeleteApiKeyCredentialProviderResponse,
type DeleteOauth2CredentialProviderResponse,
type GetApiKeyCredentialProviderResponse,
type GetOauth2CredentialProviderResponse,
type ListApiKeyCredentialProvidersResponse,
type ListOauth2CredentialProvidersResponse,
type UpdateApiKeyCredentialProviderResponse,
type UpdateOauth2CredentialProviderResponse,
} from "@aws-sdk/client-bedrock-agentcore-control";
import type {
CoreIdentityClient,
CreateApiKeyCredentialProviderInput,
CreateOauth2CredentialProviderInput,
UpdateApiKeyCredentialProviderInput,
UpdateOauth2CredentialProviderInput,
} from "../handlers/identity/types";
import type { AwsClients, CoreOptions } from "./types";
import { toClientConfig } from "./utils";
Expand Down Expand Up @@ -66,4 +78,50 @@ export class IdentityClient implements CoreIdentityClient {
.control(toClientConfig(options))
.send(new DeleteApiKeyCredentialProviderCommand({ name }));
}

async createOauth2CredentialProvider(
input: CreateOauth2CredentialProviderInput,
options: CoreOptions,
): Promise<CreateOauth2CredentialProviderResponse> {
return this.clients
.control(toClientConfig(options))
.send(new CreateOauth2CredentialProviderCommand(input));
}

async getOauth2CredentialProvider(
name: string,
options: CoreOptions,
): Promise<GetOauth2CredentialProviderResponse> {
return this.clients
.control(toClientConfig(options))
.send(new GetOauth2CredentialProviderCommand({ name }));
}

async listOauth2CredentialProviders(
nextToken: string | undefined,
maxResults: number | undefined,
options: CoreOptions,
): Promise<ListOauth2CredentialProvidersResponse> {
return this.clients
.control(toClientConfig(options))
.send(new ListOauth2CredentialProvidersCommand({ nextToken, maxResults }));
}

async updateOauth2CredentialProvider(
input: UpdateOauth2CredentialProviderInput,
options: CoreOptions,
): Promise<UpdateOauth2CredentialProviderResponse> {
return this.clients
.control(toClientConfig(options))
.send(new UpdateOauth2CredentialProviderCommand(input));
}

async deleteOauth2CredentialProvider(
name: string,
options: CoreOptions,
): Promise<DeleteOauth2CredentialProviderResponse> {
return this.clients
.control(toClientConfig(options))
.send(new DeleteOauth2CredentialProviderCommand({ name }));
}
}
Loading
Loading