fix(cli): list all non-archived agents by default and drop ls --ui

This commit is contained in:
Mohamed Boudra
2026-02-12 14:05:46 +07:00
parent 3a303d2095
commit 6053ee7c34
4 changed files with 33 additions and 49 deletions
+3 -4
View File
@@ -42,11 +42,10 @@ export function createCli(): Command {
// Primary agent commands (top-level)
program
.command('ls')
.description('List agents. By default shows background agents (without ui=true) in current directory.')
.option('-a, --all', 'Include all statuses (not just running)')
.option('-g, --global', 'Show agents from all directories (not just current)')
.description('List agents. By default excludes archived agents.')
.option('-a, --all', 'Include archived agents')
.option('-g, --global', 'Legacy no-op (kept for compatibility)')
.option('--label <key=value>', 'Filter by label (can be used multiple times)', collectMultiple, [])
.option('--ui', 'Show only UI agents (equivalent to --label ui=true)')
.option('--json', 'Output in JSON format')
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
.action(withOutput(runLsCommand))
+3 -4
View File
@@ -22,11 +22,10 @@ export function createAgentCommand(): Command {
// Primary agent commands (same as top-level)
agent
.command('ls')
.description('List agents. By default shows background agents (without ui=true) in current directory.')
.option('-a, --all', 'Include all statuses (not just running)')
.option('-g, --global', 'Show agents from all directories (not just current)')
.description('List agents. By default excludes archived agents.')
.option('-a, --all', 'Include archived agents')
.option('-g, --global', 'Legacy no-op (kept for compatibility)')
.option('--label <key=value>', 'Filter by label (can be used multiple times)', collectMultiple, [])
.option('--ui', 'Show only UI agents (equivalent to --label ui=true)')
.option('--json', 'Output in JSON format')
.option('--host <host>', 'Daemon host:port (default: localhost:6767)')
.action(withOutput(runLsCommand))
+14 -41
View File
@@ -74,26 +74,22 @@ function toListItem(agent: AgentSnapshotPayload): AgentListItem {
export type AgentLsResult = ListResult<AgentListItem>
export interface AgentLsOptions extends CommandOptions {
/** -a: Include all statuses (not just running/idle) */
/** -a: Include archived agents */
all?: boolean
/** -g: Show agents globally (not just current directory) */
/** Legacy flag retained for CLI compatibility */
global?: boolean
/** Filter by specific status */
status?: string
/** Filter by specific cwd (overrides default cwd filtering) */
/** Filter by specific cwd */
cwd?: string
/** Filter by labels (key=value format) */
label?: string[]
/** Filter to UI agents only (equivalent to --label ui=true) */
ui?: boolean
}
/**
* Agent ls command with correct semantics from design doc:
* - `paseo agent ls` → running/idle agents in current directory
* - `paseo agent ls -a` → all statuses in current directory
* - `paseo agent ls -g` → running/idle agents globally
* - `paseo agent ls -ag` → everything everywhere
* Agent ls command semantics:
* - `paseo agent ls` → all non-archived agents
* - `paseo agent ls -a` → include archived agents
*/
export async function runLsCommand(
options: AgentLsOptions,
@@ -117,39 +113,26 @@ export async function runLsCommand(
try {
let agents = await client.fetchAgents()
// Status filtering:
// By default, only show running/idle agents (not error, archived, etc.)
// With -a flag, show all statuses
// By default, exclude archived agents. `-a` includes them.
if (!options.all) {
agents = agents.filter((a) => {
// Show running and idle agents, exclude archived
return (a.status === 'running' || a.status === 'idle') && !a.archivedAt
})
agents = agents.filter((a) => !a.archivedAt)
}
// If explicit status filter is provided, use it
// If explicit status filter is provided, apply it.
if (options.status) {
agents = agents.filter((a) => a.status === options.status)
}
// Directory filtering:
// By default, only show agents in current working directory
// With -g flag, show agents globally (all directories)
if (!options.global) {
const currentCwd = options.cwd ?? process.cwd()
// Optional cwd filter.
if (options.cwd) {
const targetCwd = options.cwd.replace(/\/$/, '')
agents = agents.filter((a) => {
// Normalize paths for comparison
const agentCwd = a.cwd.replace(/\/$/, '')
const targetCwd = currentCwd.replace(/\/$/, '')
// Match exact cwd or subdirectories
return agentCwd === targetCwd || agentCwd.startsWith(targetCwd + '/')
})
}
// Label filtering:
// Parse --label flags and --ui flag
// --ui is equivalent to --label ui=true
// By default (no --ui flag), show background agents (those WITHOUT ui=true)
// Parse --label filters (key=value).
const labelFilters: Record<string, string> = {}
if (options.label) {
for (const labelStr of options.label) {
@@ -161,14 +144,9 @@ export async function runLsCommand(
}
}
}
// Add ui=true filter if --ui flag is set
if (options.ui) {
labelFilters['ui'] = 'true'
}
// Apply label filtering
// Apply label filtering only when explicitly requested.
if (Object.keys(labelFilters).length > 0) {
// Filter to agents that have ALL specified labels (AND semantics)
agents = agents.filter((a) => {
const agentLabels = a.labels
for (const [key, value] of Object.entries(labelFilters)) {
@@ -178,11 +156,6 @@ export async function runLsCommand(
}
return true
})
} else {
// Default: show background agents only (those without ui=true)
agents = agents.filter((a) => {
return a.labels['ui'] !== 'true'
})
}
await client.close()
+13
View File
@@ -16,6 +16,7 @@
* - paseo ls --json returns valid JSON (or error)
* - paseo ls -a flag is accepted
* - paseo ls -g flag is accepted
* - paseo ls does not support --ui
*/
import assert from 'node:assert'
@@ -52,6 +53,7 @@ try {
assert(result.stdout.includes('-g'), 'help should mention -g flag')
assert(result.stdout.includes('--global'), 'help should mention --global flag')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(!result.stdout.includes('--ui'), 'help should not mention --ui')
console.log('✓ paseo ls --help shows options\n')
}
@@ -138,6 +140,17 @@ try {
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ -q (quiet) flag is accepted\n')
}
// Test 9: paseo ls --ui is rejected (flag removed)
{
console.log('Test 9: paseo ls --ui is rejected')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo ls --ui`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail for removed --ui flag')
const output = result.stdout + result.stderr
assert(output.includes('unknown option'), 'should report unknown option for --ui')
console.log('✓ paseo ls --ui is rejected\n')
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true })