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
35 changes: 28 additions & 7 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, Runtime, Memory, and Identity branches
and leaves open their corresponding menus and selection flows.
- **An interactive TUI** — bare Harness, Runtime, Memory, Identity, and Gateway
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, Runtime, Memory, and
Identity branches and leaves open their interactive flows.
Commands with operation flags run headlessly. Bare Harness, Runtime, Memory,
Identity, and Gateway branches and leaves open their interactive flows.

```
agentcore # interactive TUI
Expand Down Expand Up @@ -86,6 +86,9 @@ agentcore # interactive TUI
│ ├── target
│ │ ├── get # get a Target under a Gateway
│ │ └── list # list Targets under a Gateway
│ ├── connector
│ │ ├── get # get a connector-backed Target
│ │ └── list # list connector-backed Targets
│ └── rule
│ ├── get # get a Rule under a Gateway
│ └── list # list Rules under a Gateway
Expand Down Expand Up @@ -158,6 +161,8 @@ agentcore gateway get --id <gatewayId>
agentcore gateway list --max-results 20
agentcore gateway target get --gateway-id <gatewayId> --target-id <targetId>
agentcore gateway target list --gateway-id <gatewayId> --max-results 20
agentcore gateway connector get --gateway-id <gatewayId> --id <targetId>
agentcore gateway connector list --gateway-id <gatewayId> --max-results 20
agentcore gateway rule get --gateway-id <gatewayId> --rule-id <ruleId>
agentcore gateway rule list --gateway-id <gatewayId> --max-results 20

Expand Down Expand Up @@ -340,6 +345,23 @@ agentcore identity oauth2-credential-provider list
agentcore identity oauth2-credential-provider get
```

The Gateway TUI is read-only: bare Gateway, Target, Connector, and Rule
branches and their `get`/`list` leaves open command menus and scoped selection
flows. Connector is presented as a separate resource experience while using
Gateway Target operations internally.

```bash
agentcore gateway
agentcore gateway list
agentcore gateway get
agentcore gateway target list
agentcore gateway target get
agentcore gateway connector list
agentcore gateway connector get
agentcore gateway rule list
agentcore gateway rule get
```

---

# Architecture & patterns
Expand Down Expand Up @@ -749,9 +771,8 @@ A Husky pre-commit hook runs Prettier (via lint-staged) on staged files automati

- **Cover more AgentCore resources.** The harness surface (CRUD, versions,
endpoints, invoke, exec) is fully implemented in both the CLI and the TUI;
the same patterns extend naturally to gateways, the remaining read-only
Memory data-plane operations, browser profiles, and the other AgentCore
resources.
the same patterns extend naturally to the remaining read-only Memory
data-plane operations, browser profiles, and the other AgentCore resources.
- **Implement `config`.** The `config` command is currently a stub — it should
read/write real global settings (telemetry, log level, ...) through an
injected config accessor.
79 changes: 79 additions & 0 deletions src/components/GatewayConnectorPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { TargetSummary } from "@aws-sdk/client-bedrock-agentcore-control";
import { useNavigate } from "react-router";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

interface GatewayConnectorRow extends Record<string, unknown> {
targetId: string;
name: string;
status: string;
updatedAt: string;
}

export const gatewayConnectorColumns = [
{ key: "name", header: "name", flex: true },
{ key: "status", header: "status", width: 18 },
{
key: "updatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<GatewayConnectorRow>[];

function toRow(target: TargetSummary): GatewayConnectorRow {
return {
targetId: target.targetId ?? "",
name: target.name ?? target.targetId ?? "",
status: target.status ?? "-",
updatedAt: target.updatedAt?.toISOString() ?? "-",
};
}

export interface GatewayConnectorPickerProps extends ScreenProps {
gatewayId: string;
breadcrumb: string[];
description?: string;
onSelect: (targetId: string) => void;
}

export function GatewayConnectorPicker({
ctx,
core,
gatewayId,
breadcrumb,
description,
onSelect,
}: GatewayConnectorPickerProps) {
const opts = coreOptsFromCtx(ctx);
const navigate = useNavigate();

return (
<PaginatedTablePicker
breadcrumb={breadcrumb}
description={description}
queryKey={["gateway-connectors", opts.region, gatewayId]}
loadPage={async (token, pageSize) => {
const response = await core.gateway.listGatewayConnectors(gatewayId, token, pageSize, opts);
return {
items: response.items ?? [],
nextToken: response.nextToken,
};
}}
toRow={toRow}
columns={gatewayConnectorColumns}
getValue={(row) => row.targetId}
onSelect={onSelect}
onBack={() => navigate(-1)}
loadingMessage={`Loading Connectors for Gateway ${gatewayId}…`}
errorMessage={(error) =>
`Error loading Connectors for Gateway ${gatewayId}: ${error.message}`
}
emptyMessage="This Gateway has no Connectors."
emptyPageMessage="No Connectors on this page."
/>
);
}
81 changes: 81 additions & 0 deletions src/components/GatewayPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import type { GatewaySummary } from "@aws-sdk/client-bedrock-agentcore-control";
import { useNavigate } from "react-router";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

interface GatewayRow extends Record<string, unknown> {
gatewayId: string;
name: string;
status: string;
protocol: string;
authorizer: string;
updatedAt: string;
}

export const gatewayColumns = [
{ key: "name", header: "name", flex: true },
{ key: "status", header: "status", width: 16 },
{ key: "protocol", header: "protocol", width: 12 },
{ key: "authorizer", header: "authorizer", width: 18 },
{
key: "updatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<GatewayRow>[];

function toRow(gateway: GatewaySummary): GatewayRow {
return {
gatewayId: gateway.gatewayId ?? "",
name: gateway.name ?? gateway.gatewayId ?? "",
status: gateway.status ?? "-",
protocol: gateway.protocolType ?? "unrestricted",
authorizer: gateway.authorizerType ?? "-",
updatedAt: gateway.updatedAt?.toISOString() ?? "-",
};
}

export interface GatewayPickerProps extends ScreenProps {
breadcrumb: string[];
description?: string;
onSelect: (gatewayId: string) => void;
}

export function GatewayPicker({
ctx,
core,
breadcrumb,
description,
onSelect,
}: GatewayPickerProps) {
const opts = coreOptsFromCtx(ctx);
const navigate = useNavigate();

return (
<PaginatedTablePicker
breadcrumb={breadcrumb}
description={description}
queryKey={["gateways", opts.region]}
loadPage={async (token, pageSize) => {
const response = await core.gateway.listGateways(token, pageSize, opts);
return {
items: response.items ?? [],
nextToken: response.nextToken,
};
}}
toRow={toRow}
columns={gatewayColumns}
getValue={(row) => row.gatewayId}
onSelect={onSelect}
onBack={() => navigate("/" + breadcrumb.slice(0, -1).join("/"))}
loadingMessage="Loading Gateways…"
errorMessage={(error) => `Error: ${error.message}`}
emptyMessage="No Gateways found in this Region."
emptyPageMessage="No Gateways on this page."
/>
);
}
77 changes: 77 additions & 0 deletions src/components/GatewayRulePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { GatewayRuleDetail } from "@aws-sdk/client-bedrock-agentcore-control";
import { useNavigate } from "react-router";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

interface GatewayRuleRow extends Record<string, unknown> {
ruleId: string;
priority: string;
status: string;
description: string;
}

export const gatewayRuleColumns = [
{ key: "priority", header: "priority", width: 10 },
{ key: "status", header: "status", width: 13 },
{ key: "description", header: "description", flex: true },
{
key: "ruleId",
header: "id suffix",
width: 10,
render: (value: unknown) => String(value ?? "").slice(-8),
},
] satisfies DataTableColumn<GatewayRuleRow>[];

function toRow(rule: GatewayRuleDetail): GatewayRuleRow {
return {
ruleId: rule.ruleId ?? "",
priority: rule.priority?.toString() ?? "-",
status: rule.status ?? "-",
description: rule.description ?? "-",
};
}

export interface GatewayRulePickerProps extends ScreenProps {
gatewayId: string;
breadcrumb: string[];
description?: string;
onSelect: (ruleId: string) => void;
}

export function GatewayRulePicker({
ctx,
core,
gatewayId,
breadcrumb,
description,
onSelect,
}: GatewayRulePickerProps) {
const opts = coreOptsFromCtx(ctx);
const navigate = useNavigate();

return (
<PaginatedTablePicker
breadcrumb={breadcrumb}
description={description}
queryKey={["gateway-rules", opts.region, gatewayId]}
loadPage={async (token, pageSize) => {
const response = await core.gateway.listGatewayRules(gatewayId, token, pageSize, opts);
return {
items: response.gatewayRules ?? [],
nextToken: response.nextToken,
};
}}
toRow={toRow}
columns={gatewayRuleColumns}
getValue={(row) => row.ruleId}
onSelect={onSelect}
onBack={() => navigate(-1)}
loadingMessage={`Loading Rules for Gateway ${gatewayId}…`}
errorMessage={(error) => `Error loading Rules for Gateway ${gatewayId}: ${error.message}`}
emptyMessage="This Gateway has no Rules."
emptyPageMessage={`No Rules on this page for Gateway ${gatewayId}.`}
/>
);
}
80 changes: 80 additions & 0 deletions src/components/GatewayTargetPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { TargetSummary } from "@aws-sdk/client-bedrock-agentcore-control";
import { useNavigate } from "react-router";
import type { ScreenProps } from "../handlers/types";
import { coreOptsFromCtx } from "../handlers/utils";
import { formatTimestamp } from "./formatTimestamp";
import { PaginatedTablePicker } from "./PaginatedTablePicker";
import type { DataTableColumn } from "./ui/data-table";

interface GatewayTargetRow extends Record<string, unknown> {
targetId: string;
name: string;
type: string;
status: string;
updatedAt: string;
}

export const gatewayTargetColumns = [
{ key: "name", header: "name", flex: true },
{ key: "type", header: "type", width: 18 },
{ key: "status", header: "status", width: 18 },
{
key: "updatedAt",
header: "updated UTC",
width: 16,
render: formatTimestamp,
},
] satisfies DataTableColumn<GatewayTargetRow>[];

function toRow(target: TargetSummary): GatewayTargetRow {
return {
targetId: target.targetId ?? "",
name: target.name ?? target.targetId ?? "",
type: target.targetType ?? "-",
status: target.status ?? "-",
updatedAt: target.updatedAt?.toISOString() ?? "-",
};
}

export interface GatewayTargetPickerProps extends ScreenProps {
gatewayId: string;
breadcrumb: string[];
description?: string;
onSelect: (targetId: string) => void;
}

export function GatewayTargetPicker({
ctx,
core,
gatewayId,
breadcrumb,
description,
onSelect,
}: GatewayTargetPickerProps) {
const opts = coreOptsFromCtx(ctx);
const navigate = useNavigate();

return (
<PaginatedTablePicker
breadcrumb={breadcrumb}
description={description}
queryKey={["gateway-targets", opts.region, gatewayId]}
loadPage={async (token, pageSize) => {
const response = await core.gateway.listGatewayTargets(gatewayId, token, pageSize, opts);
return {
items: response.items ?? [],
nextToken: response.nextToken,
};
}}
toRow={toRow}
columns={gatewayTargetColumns}
getValue={(row) => row.targetId}
onSelect={onSelect}
onBack={() => navigate(-1)}
loadingMessage={`Loading Targets for Gateway ${gatewayId}…`}
errorMessage={(error) => `Error loading Targets for Gateway ${gatewayId}: ${error.message}`}
emptyMessage="This Gateway has no Targets."
emptyPageMessage={`No Targets on this page for Gateway ${gatewayId}.`}
/>
);
}
Loading
Loading