-
Notifications
You must be signed in to change notification settings - Fork 0
feat(cli): add some (hidden) agent handling commands #187
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| /** | ||
| * List agents command | ||
| */ | ||
|
|
||
| import chalk from "chalk"; | ||
| import { listAgents, type Agent } from "../../services/agentService.js"; | ||
| import { output, outputError } from "../../utils/output.js"; | ||
| import { formatTimeAgo } from "../../utils/time.js"; | ||
|
|
||
| interface ListOptions { | ||
| full?: boolean; | ||
| name?: string; | ||
| search?: string; | ||
| public?: boolean; | ||
| private?: boolean; | ||
| output?: string; | ||
| } | ||
|
|
||
| // Column widths (NAME is dynamic, takes remaining space) | ||
| const COL_VERSION = 14; | ||
| const COL_VISIBILITY = 10; | ||
| const COL_ID = 30; | ||
| const COL_CREATED = 10; | ||
| const FIXED_WIDTH = COL_VERSION + COL_VISIBILITY + COL_ID + COL_CREATED + 4; // 4 for spacing | ||
|
|
||
| function truncate(str: string, maxLen: number): string { | ||
| if (str.length <= maxLen) return str; | ||
| return str.slice(0, maxLen - 1) + "…"; | ||
| } | ||
|
|
||
| function printTable(agents: Agent[]): void { | ||
| if (agents.length === 0) { | ||
| console.log(chalk.dim("No agents found")); | ||
| return; | ||
| } | ||
|
|
||
| const termWidth = process.stdout.columns || 120; | ||
| const nameWidth = Math.max(10, termWidth - FIXED_WIDTH); | ||
|
|
||
| // Header | ||
| const header = | ||
| "NAME".padEnd(nameWidth) + | ||
| " " + | ||
| "VERSION".padEnd(COL_VERSION) + | ||
| " " + | ||
| "VISIBILITY".padEnd(COL_VISIBILITY) + | ||
| " " + | ||
| "ID".padEnd(COL_ID) + | ||
| " " + | ||
| "CREATED".padEnd(COL_CREATED); | ||
| console.log(chalk.bold(header)); | ||
| console.log(chalk.dim("─".repeat(Math.min(header.length, termWidth)))); | ||
|
|
||
| for (const agent of agents) { | ||
| const name = truncate(agent.name, nameWidth).padEnd(nameWidth); | ||
| const version = truncate(agent.version, COL_VERSION).padEnd(COL_VERSION); | ||
| const visibility = (agent.is_public ? "public" : "private").padEnd( | ||
| COL_VISIBILITY, | ||
| ); | ||
| const visibilityColored = agent.is_public | ||
| ? chalk.green(visibility) | ||
| : chalk.dim(visibility); | ||
| const id = truncate(agent.id, COL_ID).padEnd(COL_ID); | ||
| const created = formatTimeAgo(agent.create_time_ms).padEnd(COL_CREATED); | ||
|
|
||
| console.log( | ||
| `${name} ${version} ${visibilityColored} ${chalk.dim(id)} ${chalk.dim(created)}`, | ||
| ); | ||
| } | ||
|
|
||
| console.log(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this just for the newline?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep! |
||
| console.log( | ||
| chalk.dim(`${agents.length} agent${agents.length !== 1 ? "s" : ""}`), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Keep only the most recently created agent for each name. | ||
| */ | ||
| function keepLatestPerName(agents: Agent[]): Agent[] { | ||
| const latestByName = new Map<string, Agent>(); | ||
| for (const agent of agents) { | ||
| const existing = latestByName.get(agent.name); | ||
| if (!existing || agent.create_time_ms > existing.create_time_ms) { | ||
| latestByName.set(agent.name, agent); | ||
| } | ||
| } | ||
| return Array.from(latestByName.values()); | ||
| } | ||
|
|
||
| export async function listAgentsCommand(options: ListOptions): Promise<void> { | ||
| try { | ||
| const result = await listAgents({ | ||
| publicOnly: options.public, | ||
| privateOnly: options.private, | ||
| name: options.name, | ||
| search: options.search, | ||
| }); | ||
|
|
||
| const agents = options.full | ||
| ? result.agents | ||
| : keepLatestPerName(result.agents); | ||
|
|
||
| const format = options.output || "text"; | ||
| if (format !== "text") { | ||
| output(agents, { format, defaultFormat: "json" }); | ||
| } else { | ||
| printTable(agents); | ||
| } | ||
| } catch (error) { | ||
| outputError("Failed to list agents", error); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| /** | ||
| * Show agent details command | ||
| */ | ||
|
|
||
| import { | ||
| getAgent, | ||
| listAgents, | ||
| type Agent, | ||
| } from "../../services/agentService.js"; | ||
| import { output, outputError } from "../../utils/output.js"; | ||
|
|
||
| interface ShowOptions { | ||
| output?: string; | ||
| } | ||
|
|
||
| /** | ||
| * Determine whether the input looks like an agent ID (starts with "agt_") | ||
| * vs. a name, then retrieve the corresponding agent. | ||
| */ | ||
| async function resolveAgent(idOrName: string): Promise<Agent> { | ||
| if (idOrName.startsWith("agt_")) { | ||
| return getAgent(idOrName); | ||
| } | ||
|
|
||
| // Look up by name — fetch all versions with this name and pick the latest. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't a problem with this code, but reading through this makes me wonder if we should add a backend endpoint for this purpose
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I considered this as well. At some point this might make sense, especially if we have lots of versions for public agents. (eg, re-publish every week, across 100s of agents?). It depends on how often this 'list' feature gets used; it might be fairly rare, in which case it doesn't much matter? |
||
| const result = await listAgents({ name: idOrName }); | ||
| const matches = result.agents.filter((a) => a.name === idOrName); | ||
| if (matches.length === 0) { | ||
| throw new Error(`No agent found with name: ${idOrName}`); | ||
| } | ||
|
|
||
| // Pick the most recently created version | ||
| matches.sort((a, b) => b.create_time_ms - a.create_time_ms); | ||
| return matches[0]; | ||
| } | ||
|
|
||
| export async function showAgentCommand( | ||
| idOrName: string, | ||
| options: ShowOptions, | ||
| ): Promise<void> { | ||
| try { | ||
| const agent = await resolveAgent(idOrName); | ||
| output(agent, { format: options.output, defaultFormat: "text" }); | ||
| } catch (error) { | ||
| outputError("Failed to get agent", error); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be
maxLen -1 - "...".length?