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