Skip to content
Merged
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
5 changes: 5 additions & 0 deletions src/commands/agent/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
import { useListSearch } from "../../hooks/useListSearch.js";
import { useNavigation } from "../../store/navigationStore.js";
import { openInBrowser } from "../../utils/browser.js";
import { getAgentUrl } from "../../utils/url.js";

interface ListOptions {
full?: boolean;
Expand Down Expand Up @@ -156,7 +158,7 @@
);
}

function printTable(agents: Agent[], isPublic: boolean): void {

Check warning on line 161 in src/commands/agent/list.tsx

View workflow job for this annotation

GitHub Actions / lint

'printTable' is defined but never used. Allowed unused vars must match /^_/u
if (isPublic) {
console.log(
chalk.dim("Showing PUBLIC agents. Use --private to see private agents"),
Expand Down Expand Up @@ -532,6 +534,8 @@
setSelectedOperation(0);
} else if (input === "c" && activeTab === "private") {
navigate("agent-create");
} else if (input === "o" && selectedAgentItem) {
openInBrowser(getAgentUrl(selectedAgentItem.id));
} else if (input === "/") {
search.enterSearchMode();
} else if (key.escape) {
Expand Down Expand Up @@ -771,6 +775,7 @@
{ key: "Tab", label: "Switch tab" },
{ key: "Enter", label: "Details" },
{ key: "a", label: "Actions" },
{ key: "o", label: "Browser" },
{ key: "c", label: "Create", condition: activeTab === "private" },
{ key: "/", label: "Search" },
{ key: "Esc", label: "Back" },
Expand Down
5 changes: 5 additions & 0 deletions src/commands/axon/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import { useCursorPagination } from "../../hooks/useCursorPagination.js";
import { useListSearch } from "../../hooks/useListSearch.js";
import { useNavigation } from "../../store/navigationStore.js";
import { openInBrowser } from "../../utils/browser.js";
import { getAxonUrl } from "../../utils/url.js";

// ─── CLI ─────────────────────────────────────────────────────────────────────

Expand All @@ -42,7 +44,7 @@

if (options.startingAfter) {
const pageLimit = maxResults === Infinity ? CLI_PAGE_SIZE : maxResults;
const { axons: page, hasMore } = await listActiveAxons({

Check warning on line 47 in src/commands/axon/list.tsx

View workflow job for this annotation

GitHub Actions / lint

'hasMore' is assigned a value but never used. Allowed unused vars must match /^_/u
limit: pageLimit,
startingAfter: options.startingAfter,
});
Expand Down Expand Up @@ -140,7 +142,7 @@
totalCount,
nextPage,
prevPage,
refresh,

Check warning on line 145 in src/commands/axon/list.tsx

View workflow job for this annotation

GitHub Actions / lint

'refresh' is assigned a value but never used. Allowed unused vars must match /^_/u
} = useCursorPagination({
fetchPage,
pageSize: PAGE_SIZE,
Expand Down Expand Up @@ -284,6 +286,8 @@
} else if (input === "a" && selectedAxonItem) {
setShowPopup(true);
setSelectedOperation(0);
} else if (input === "o" && selectedAxonItem) {
openInBrowser(getAxonUrl(selectedAxonItem.id));
} else if (input === "/") {
search.enterSearchMode();
} else if (key.escape) {
Expand Down Expand Up @@ -430,6 +434,7 @@
},
{ key: "Enter", label: "Details" },
{ key: "a", label: "Actions" },
{ key: "o", label: "Browser" },
{ key: "/", label: "Search" },
{ key: "Esc", label: "Back" },
]}
Expand Down
14 changes: 11 additions & 3 deletions src/components/ResourceDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ export function ResourceDetailPage<T>({
onBack,
buildDetailLines,
additionalContent,
extraKeybinds,
extraNavTips,
}: ResourceDetailPageProps<T>) {
const isMounted = React.useRef(true);
const { navigate } = useNavigation();
Expand All @@ -167,9 +169,9 @@ export function ResourceDetailPage<T>({
const [copyStatus, setCopyStatus] = React.useState<string | null>(null);

// Copy to clipboard with status feedback
const handleCopy = React.useCallback(async (text: string) => {
const handleCopy = React.useCallback(async (text: string, label?: string) => {
const status = await copyToClipboard(text);
setCopyStatus(status);
setCopyStatus(label ? `${label} copied!` : status);
setTimeout(() => setCopyStatus(null), 2000);
}, []);

Expand Down Expand Up @@ -393,7 +395,8 @@ export function ResourceDetailPage<T>({
bindings: {
q: onBack,
escape: onBack,
c: () => handleCopy(getId(resource)),
c: () => handleCopy(getId(resource), "ID"),
y: () => handleCopy(getDisplayName(resource), "Name"),
...(buildDetailLines
? {
i: () => {
Expand All @@ -411,6 +414,7 @@ export function ResourceDetailPage<T>({
},
enter: handleEnter,
...(getUrl ? { o: handleOpenInBrowser } : {}),
...(extraKeybinds ? extraKeybinds({ copy: handleCopy }) : {}),
},
onUnmatched: (input) => {
// Operation shortcuts work from anywhere (all ops, including those in "View rest")
Expand Down Expand Up @@ -452,6 +456,8 @@ export function ResourceDetailPage<T>({
operationsStartIndex,
onOperation,
getId,
getDisplayName,
extraKeybinds,
],
);

Expand Down Expand Up @@ -808,6 +814,8 @@ export function ResourceDetailPage<T>({
: "Execute",
},
{ key: "c", label: "Copy ID" },
{ key: "y", label: "Copy Name" },
...(extraNavTips || []),
{ key: "i", label: "Full Details", condition: !!buildDetailLines },
{ key: "o", label: "Browser", condition: !!getUrl },
{ key: "q/Ctrl+C", label: "Back/Quit" },
Expand Down
7 changes: 7 additions & 0 deletions src/components/resourceDetailTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import React from "react";
import type { ScreenName, RouteParams } from "../store/navigationStore.js";
import type { NavigationTip } from "./NavigationTips.js";

// ---------------------------------------------------------------------------
// Detail field types
Expand Down Expand Up @@ -119,4 +120,10 @@ export interface ResourceDetailPageProps<T> {
buildDetailLines?: (resource: T) => React.ReactElement[];
/** Optional: Additional content to render after details section */
additionalContent?: React.ReactNode;
/** Optional: Extra keybinds added to the main view. Receives a copy helper for clipboard feedback. */
extraKeybinds?: (helpers: {
copy: (text: string, label?: string) => void;
}) => Record<string, () => void>;
/** Optional: Extra navigation tips shown in the footer alongside the standard ones */
extraNavTips?: NavigationTip[];
}
2 changes: 2 additions & 0 deletions src/screens/AgentDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ErrorMessage } from "../components/ErrorMessage.js";
import { Breadcrumb } from "../components/Breadcrumb.js";
import { ConfirmationPrompt } from "../components/ConfirmationPrompt.js";
import { colors } from "../utils/theme.js";
import { getAgentUrl } from "../utils/url.js";

interface AgentDetailScreenProps {
agentId?: string;
Expand Down Expand Up @@ -332,6 +333,7 @@ export function AgentDetailScreen({ agentId }: AgentDetailScreenProps) {
resourceType="Agents"
getDisplayName={(a) => a.name}
getId={(a) => a.id}
getUrl={(a) => getAgentUrl(a.id)}
getStatus={() => (agent.is_public ? "public" : "private")}
detailSections={detailSections}
operations={operations}
Expand Down
2 changes: 2 additions & 0 deletions src/screens/AxonDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SpinnerComponent } from "../components/Spinner.js";
import { ErrorMessage } from "../components/ErrorMessage.js";
import { Breadcrumb } from "../components/Breadcrumb.js";
import { colors } from "../utils/theme.js";
import { getAxonUrl } from "../utils/url.js";

interface AxonDetailScreenProps {
axonId?: string;
Expand Down Expand Up @@ -143,6 +144,7 @@ export function AxonDetailScreen({ axonId }: AxonDetailScreenProps) {
resourceType="Axons"
getDisplayName={(a) => a.name ?? a.id}
getId={(a) => a.id}
getUrl={(a) => getAxonUrl(a.id)}
getStatus={() => "active"}
detailSections={detailSections}
operations={[]}
Expand Down
2 changes: 2 additions & 0 deletions src/screens/BenchmarkDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { SpinnerComponent } from "../components/Spinner.js";
import { ErrorMessage } from "../components/ErrorMessage.js";
import { Breadcrumb } from "../components/Breadcrumb.js";
import { colors } from "../utils/theme.js";
import { getBenchmarkUrl } from "../utils/url.js";

interface BenchmarkDetailScreenProps {
benchmarkId?: string;
Expand Down Expand Up @@ -275,6 +276,7 @@ export function BenchmarkDetailScreen({
resourceType="Benchmark Definitions"
getDisplayName={(b) => b.name || b.id}
getId={(b) => b.id}
getUrl={(b) => getBenchmarkUrl(b.id, !!b.is_public)}
getStatus={(b) => (b as any).status}
detailSections={detailSections}
operations={operations}
Expand Down
5 changes: 5 additions & 0 deletions src/screens/BenchmarkListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import {
listPublicBenchmarks,
} from "../services/benchmarkService.js";
import type { Benchmark } from "../store/benchmarkStore.js";
import { openInBrowser } from "../utils/browser.js";
import { getBenchmarkUrl } from "../utils/url.js";

export function BenchmarkListScreen() {
const { exit: inkExit } = useApp();
Expand Down Expand Up @@ -281,6 +283,8 @@ export function BenchmarkListScreen() {
} else if (input === "a" && selectedBenchmark) {
setShowPopup(true);
setSelectedOperation(0);
} else if (input === "o" && selectedBenchmark) {
openInBrowser(getBenchmarkUrl(selectedBenchmark.id, showPublic));
} else if (input === "s" && selectedBenchmark) {
// Quick shortcut to create a job
navigate("benchmark-job-create", {
Expand Down Expand Up @@ -463,6 +467,7 @@ export function BenchmarkListScreen() {
{ key: "Enter", label: "Details" },
{ key: "s", label: "Create Job" },
{ key: "a", label: "Actions" },
{ key: "o", label: "Browser" },
{ key: "Tab", label: "Switch tab" },
{ key: "/", label: "Search" },
{ key: "Esc", label: "Back" },
Expand Down
2 changes: 2 additions & 0 deletions src/screens/BenchmarkRunDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
createComponentColumn,
} from "../components/Table.js";
import { colors } from "../utils/theme.js";
import { getBenchmarkRunUrl } from "../utils/url.js";

interface BenchmarkRunDetailScreenProps {
benchmarkRunId?: string;
Expand Down Expand Up @@ -621,6 +622,7 @@ export function BenchmarkRunDetailScreen({
resourceType="Benchmark Runs"
getDisplayName={(r) => r.name || r.id}
getId={(r) => r.id}
getUrl={(r) => getBenchmarkRunUrl(r.id, r.benchmark_id)}
getStatus={(r) => r.state}
detailSections={detailSections}
operations={operations}
Expand Down
7 changes: 7 additions & 0 deletions src/screens/BenchmarkRunListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { useCursorPagination } from "../hooks/useCursorPagination.js";
import { useListSearch } from "../hooks/useListSearch.js";
import { listBenchmarkRuns } from "../services/benchmarkService.js";
import type { BenchmarkRun } from "../store/benchmarkStore.js";
import { openInBrowser } from "../utils/browser.js";
import { getBenchmarkRunUrl } from "../utils/url.js";

export function BenchmarkRunListScreen() {
const { exit: inkExit } = useApp();
Expand Down Expand Up @@ -272,6 +274,10 @@ export function BenchmarkRunListScreen() {
} else if (input === "a" && selectedRun) {
setShowPopup(true);
setSelectedOperation(0);
} else if (input === "o" && selectedRun) {
openInBrowser(
getBenchmarkRunUrl(selectedRun.id, selectedRun.benchmark_id),
);
} else if (input === "j") {
// Quick shortcut to create a new job
navigate("benchmark-job-create");
Expand Down Expand Up @@ -433,6 +439,7 @@ export function BenchmarkRunListScreen() {
{ key: "Enter", label: "Details" },
{ key: "j", label: "New Job" },
{ key: "a", label: "Actions" },
{ key: "o", label: "Browser" },
{ key: "/", label: "Search" },
{ key: "Esc", label: "Back" },
]}
Expand Down
6 changes: 6 additions & 0 deletions src/screens/McpConfigDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,12 @@ export function McpConfigDetailScreen({
onOperation={handleOperation}
onBack={goBack}
buildDetailLines={buildDetailLines}
extraKeybinds={({ copy }) => ({
h: () => {
copy(config.endpoint, "Endpoint");
},
})}
extraNavTips={[{ key: "h", label: "Copy Endpoint" }]}
/>
);
}
2 changes: 2 additions & 0 deletions src/screens/ScenarioRunDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { SpinnerComponent } from "../components/Spinner.js";
import { ErrorMessage } from "../components/ErrorMessage.js";
import { Breadcrumb, type BreadcrumbItem } from "../components/Breadcrumb.js";
import { colors } from "../utils/theme.js";
import { getScenarioRunUrl } from "../utils/url.js";

interface ScenarioRunDetailScreenProps {
scenarioRunId?: string;
Expand Down Expand Up @@ -340,6 +341,7 @@ export function ScenarioRunDetailScreen({
resourceType="Scenario Runs"
getDisplayName={(r) => r.name || r.id}
getId={(r) => r.id}
getUrl={(r) => getScenarioRunUrl(r.scenario_id, r.id)}
getStatus={(r) => r.state}
detailSections={detailSections}
operations={operations}
Expand Down
5 changes: 5 additions & 0 deletions src/screens/ScenarioRunListScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { useCursorPagination } from "../hooks/useCursorPagination.js";
import { useListSearch } from "../hooks/useListSearch.js";
import { listScenarioRuns } from "../services/benchmarkService.js";
import type { ScenarioRun } from "../store/benchmarkStore.js";
import { openInBrowser } from "../utils/browser.js";
import { getScenarioRunUrl } from "../utils/url.js";

interface ScenarioRunListScreenProps {
benchmarkRunId?: string;
Expand Down Expand Up @@ -269,6 +271,8 @@ export function ScenarioRunListScreen({
} else if (input === "a" && selectedRun) {
setShowPopup(true);
setSelectedOperation(0);
} else if (input === "o" && selectedRun) {
openInBrowser(getScenarioRunUrl(selectedRun.scenario_id, selectedRun.id));
} else if (input === "/") {
search.enterSearchMode();
} else if (key.escape) {
Expand Down Expand Up @@ -424,6 +428,7 @@ export function ScenarioRunListScreen({
},
{ key: "Enter", label: "Details" },
{ key: "a", label: "Actions" },
{ key: "o", label: "Browser" },
{ key: "/", label: "Search" },
{ key: "Esc", label: "Back" },
]}
Expand Down
46 changes: 46 additions & 0 deletions src/utils/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,52 @@ export function getBlueprintUrl(blueprintId: string): string {
return `${platformBaseUrl()}/blueprints/${blueprintId}`;
}

/**
* Generate an agent URL for the given agent ID
*/
export function getAgentUrl(agentId: string): string {
return `${platformBaseUrl()}/agents/${agentId}`;
}

/**
* Generate an axon URL for the given axon ID
*/
export function getAxonUrl(axonId: string): string {
return `${platformBaseUrl()}/axons/${axonId}`;
}

/**
* Generate a benchmark URL for the given benchmark ID
*/
export function getBenchmarkUrl(
benchmarkId: string,
isPublic: boolean,
): string {
const segment = isPublic ? "public" : "custom";
return `${platformBaseUrl()}/benchmarks/${segment}/${benchmarkId}`;
}

/**
* Generate a benchmark run URL for the given benchmark run ID
*/
export function getBenchmarkRunUrl(
benchmarkRunId: string,
benchmarkId?: string | null,
): string {
const bmSegment = benchmarkId ?? "single";
return `${platformBaseUrl()}/benchmarks/custom/${bmSegment}/runs/${benchmarkRunId}`;
}

/**
* Generate a scenario run URL for the given scenario and run IDs
*/
export function getScenarioRunUrl(
scenarioId: string,
scenarioRunId: string,
): string {
return `${platformBaseUrl()}/scenarios/${scenarioId}/runs/${scenarioRunId}`;
}

/**
* Generate a settings URL
*/
Expand Down
Loading