refactor(cli): rename ps to ls and add top-level agent commands

This commit is contained in:
Mohamed Boudra
2026-01-29 10:41:19 +07:00
parent 7d4636ecff
commit dfda6daeed
46 changed files with 4473 additions and 542 deletions
@@ -1,22 +1,21 @@
#!/usr/bin/env npx tsx
/**
* Phase 3: Agent PS Command Tests
* Phase 3: LS Command Tests
*
* Tests the agent ps command - listing agents.
* Tests the ls command - listing agents (top-level command).
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - JSON output format
*
* Tests:
* - agent --help shows subcommands
* - agent ps --help shows options
* - agent ps returns empty list or error when no daemon
* - agent ps --format json returns valid JSON (or error)
* - agent ps -a flag is accepted
* - agent ps --status flag is accepted
* - agent ps --cwd flag is accepted
* - paseo --help shows ls command
* - paseo ls --help shows options
* - paseo ls returns empty list or error when no daemon
* - paseo ls --format json returns valid JSON (or error)
* - paseo ls -a flag is accepted
* - paseo ls -g flag is accepted
*/
import assert from 'node:assert'
@@ -27,40 +26,40 @@ import { join } from 'path'
$.verbose = false
console.log('=== Agent PS Command Tests ===\n')
console.log('=== LS Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: agent --help shows subcommands
// Test 1: paseo --help shows ls command
{
console.log('Test 1: agent --help shows subcommands')
const result = await $`npx paseo agent --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent --help should exit 0')
assert(result.stdout.includes('ps'), 'help should mention ps subcommand')
console.log('✓ agent --help shows subcommands\n')
console.log('Test 1: paseo --help shows ls command')
const result = await $`npx paseo --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'paseo --help should exit 0')
assert(result.stdout.includes('ls'), 'help should mention ls command')
console.log('✓ paseo --help shows ls command\n')
}
// Test 2: agent ps --help shows options
// Test 2: paseo ls --help shows options
{
console.log('Test 2: agent ps --help shows options')
const result = await $`npx paseo agent ps --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent ps --help should exit 0')
console.log('Test 2: paseo ls --help shows options')
const result = await $`npx paseo ls --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'paseo ls --help should exit 0')
assert(result.stdout.includes('-a'), 'help should mention -a flag')
assert(result.stdout.includes('--all'), 'help should mention --all flag')
assert(result.stdout.includes('--status'), 'help should mention --status option')
assert(result.stdout.includes('--cwd'), 'help should mention --cwd option')
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')
console.log('✓ agent ps --help shows options\n')
console.log('✓ paseo ls --help shows options\n')
}
// Test 3: agent ps returns error when no daemon running
// Test 3: paseo ls returns error when no daemon running
{
console.log('Test 3: agent ps handles daemon not running')
console.log('Test 3: paseo ls handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent ps`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo ls`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
@@ -69,14 +68,14 @@ try {
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ agent ps handles daemon not running\n')
console.log('✓ paseo ls handles daemon not running\n')
}
// Test 4: agent ps --format json returns valid JSON error
// Test 4: paseo ls --format json returns valid JSON error
{
console.log('Test 4: agent ps --format json handles errors')
console.log('Test 4: paseo ls --format json handles errors')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent ps --format json`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo ls --format json`.nothrow()
// Should still fail (daemon not running)
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
// But output should be valid JSON if present
@@ -84,56 +83,56 @@ try {
if (output.length > 0) {
try {
JSON.parse(output)
console.log('✓ agent ps --format json outputs valid JSON error\n')
console.log('✓ paseo ls --format json outputs valid JSON error\n')
} catch {
// Empty or stderr-only output is acceptable
console.log('✓ agent ps --format json handled error (output may be in stderr)\n')
console.log('✓ paseo ls --format json handled error (output may be in stderr)\n')
}
} else {
console.log('✓ agent ps --format json handled error gracefully\n')
console.log('✓ paseo ls --format json handled error gracefully\n')
}
}
// Test 5: agent ps -a flag is accepted
// Test 5: paseo ls -a flag is accepted
{
console.log('Test 5: agent ps -a flag is accepted')
console.log('Test 5: paseo ls -a flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent ps -a`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo ls -a`.nothrow()
// Will fail due to no daemon, but flag should be parsed without error
// (no "unknown option" error)
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -a flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent ps -a flag is accepted\n')
console.log('✓ paseo ls -a flag is accepted\n')
}
// Test 6: agent ps --status flag is accepted
// Test 6: paseo ls -g flag is accepted
{
console.log('Test 6: agent ps --status flag is accepted')
console.log('Test 6: paseo ls -g flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent ps --status running`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo ls -g`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --status flag')
assert(!output.includes('unknown option'), 'should accept -g flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent ps --status flag is accepted\n')
console.log('✓ paseo ls -g flag is accepted\n')
}
// Test 7: agent ps --cwd flag is accepted
// Test 7: paseo ls -ag combined flags are accepted
{
console.log('Test 7: agent ps --cwd flag is accepted')
console.log('Test 7: paseo ls -ag combined flags are accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent ps --cwd /tmp`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo ls -ag`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --cwd flag')
assert(!output.includes('unknown option'), 'should accept -ag flags')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent ps --cwd flag is accepted\n')
console.log('✓ paseo ls -ag combined flags are accepted\n')
}
// Test 8: -q (quiet) flag is accepted globally
{
console.log('Test 8: -q (quiet) flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q agent ps`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q ls`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
@@ -144,4 +143,4 @@ try {
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All agent ps tests passed ===')
console.log('=== All ls tests passed ===')
+56 -56
View File
@@ -1,23 +1,23 @@
#!/usr/bin/env npx tsx
/**
* Phase 4: Agent Run Command Tests
* Phase 4: Run Command Tests
*
* Tests the agent run command - creating and running agents with tasks.
* Tests the run command - creating and running agents with tasks (top-level command).
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - agent run --help shows options
* - agent run requires prompt argument
* - agent run handles daemon not running
* - agent run -d flag is accepted
* - agent run --name flag is accepted
* - agent run --provider flag is accepted
* - agent run --mode flag is accepted
* - agent run --cwd flag is accepted
* - run --help shows options
* - run requires prompt argument
* - run handles daemon not running
* - run -d flag is accepted
* - run --name flag is accepted
* - run --provider flag is accepted
* - run --mode flag is accepted
* - run --cwd flag is accepted
*/
import assert from 'node:assert'
@@ -28,18 +28,18 @@ import { join } from 'path'
$.verbose = false
console.log('=== Agent Run Command Tests ===\n')
console.log('=== Run Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: agent run --help shows options
// Test 1: run --help shows options
{
console.log('Test 1: agent run --help shows options')
const result = await $`npx paseo agent run --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent run --help should exit 0')
console.log('Test 1: run --help shows options')
const result = await $`npx paseo run --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'run --help should exit 0')
assert(result.stdout.includes('-d'), 'help should mention -d flag')
assert(result.stdout.includes('--detach'), 'help should mention --detach flag')
assert(result.stdout.includes('--name'), 'help should mention --name option')
@@ -48,14 +48,14 @@ try {
assert(result.stdout.includes('--cwd'), 'help should mention --cwd option')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('<prompt>'), 'help should mention prompt argument')
console.log('✓ agent run --help shows options\n')
console.log('✓ run --help shows options\n')
}
// Test 2: agent run requires prompt argument
// Test 2: run requires prompt argument
{
console.log('Test 2: agent run requires prompt argument')
console.log('Test 2: run requires prompt argument')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent run`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo run`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without prompt')
const output = result.stdout + result.stderr
// Commander should complain about missing argument
@@ -64,14 +64,14 @@ try {
output.toLowerCase().includes('required') ||
output.toLowerCase().includes('argument')
assert(hasMissingArg, 'error should mention missing argument')
console.log('✓ agent run requires prompt argument\n')
console.log('✓ run requires prompt argument\n')
}
// Test 3: agent run handles daemon not running
// Test 3: run handles daemon not running
{
console.log('Test 3: agent run handles daemon not running')
console.log('Test 3: run handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent run "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo run "test prompt"`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
@@ -80,97 +80,97 @@ try {
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ agent run handles daemon not running\n')
console.log('✓ run handles daemon not running\n')
}
// Test 4: agent run -d flag is accepted
// Test 4: run -d flag is accepted
{
console.log('Test 4: agent run -d flag is accepted')
console.log('Test 4: run -d flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent run -d "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo run -d "test prompt"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -d flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent run -d flag is accepted\n')
console.log('✓ run -d flag is accepted\n')
}
// Test 5: agent run --name flag is accepted
// Test 5: run --name flag is accepted
{
console.log('Test 5: agent run --name flag is accepted')
console.log('Test 5: run --name flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent run --name "test-agent" "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo run --name "test-agent" "test prompt"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --name flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent run --name flag is accepted\n')
console.log('✓ run --name flag is accepted\n')
}
// Test 6: agent run --provider flag is accepted
// Test 6: run --provider flag is accepted
{
console.log('Test 6: agent run --provider flag is accepted')
console.log('Test 6: run --provider flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent run --provider codex "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo run --provider codex "test prompt"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --provider flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent run --provider flag is accepted\n')
console.log('✓ run --provider flag is accepted\n')
}
// Test 7: agent run --mode flag is accepted
// Test 7: run --mode flag is accepted
{
console.log('Test 7: agent run --mode flag is accepted')
console.log('Test 7: run --mode flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent run --mode bypass "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo run --mode bypass "test prompt"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --mode flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent run --mode flag is accepted\n')
console.log('✓ run --mode flag is accepted\n')
}
// Test 8: agent run --cwd flag is accepted
// Test 8: run --cwd flag is accepted
{
console.log('Test 8: agent run --cwd flag is accepted')
console.log('Test 8: run --cwd flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent run --cwd /tmp "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo run --cwd /tmp "test prompt"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --cwd flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent run --cwd flag is accepted\n')
console.log('✓ run --cwd flag is accepted\n')
}
// Test 9: -q (quiet) flag is accepted with agent run
// Test 9: -q (quiet) flag is accepted with run
{
console.log('Test 9: -q (quiet) flag is accepted with agent run')
console.log('Test 9: -q (quiet) flag is accepted with run')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q agent run -d "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q run -d "test prompt"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ -q (quiet) flag is accepted with agent run\n')
console.log('✓ -q (quiet) flag is accepted with run\n')
}
// Test 10: Combined flags work together
{
console.log('Test 10: Combined flags work together')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q agent run -d --name "test-fixer" --provider claude --mode bypass --cwd /tmp "Fix the tests"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q run -d --name "test-fixer" --provider claude --mode bypass --cwd /tmp "Fix the tests"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept all combined flags')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ Combined flags work together\n')
}
// Test 11: agent shows run in subcommands
// Test 11: paseo --help shows run command
{
console.log('Test 11: agent --help shows run subcommand')
const result = await $`npx paseo agent --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent --help should exit 0')
assert(result.stdout.includes('run'), 'help should mention run subcommand')
console.log('✓ agent --help shows run subcommand\n')
console.log('Test 11: paseo --help shows run command')
const result = await $`npx paseo --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'paseo --help should exit 0')
assert(result.stdout.includes('run'), 'help should mention run command')
console.log('✓ paseo --help shows run command\n')
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All agent run tests passed ===')
console.log('=== All run tests passed ===')
+46 -46
View File
@@ -1,19 +1,19 @@
#!/usr/bin/env npx tsx
/**
* Phase 5: Agent Send Command Tests
* Phase 5: Send Command Tests
*
* Tests the agent send command - sending messages to existing agents.
* Tests the send command - sending messages to existing agents (top-level command).
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - agent send --help shows options
* - agent send requires id and prompt arguments
* - agent send handles daemon not running
* - agent send --no-wait flag is accepted
* - send --help shows options
* - send requires id and prompt arguments
* - send handles daemon not running
* - send --no-wait flag is accepted
* - agent shows send in subcommands
*/
@@ -25,18 +25,18 @@ import { join } from 'path'
$.verbose = false
console.log('=== Agent Send Command Tests ===\n')
console.log('=== Send Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: agent send --help shows options
// Test 1: send --help shows options
{
console.log('Test 1: agent send --help shows options')
const result = await $`npx paseo agent send --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent send --help should exit 0')
console.log('Test 1: send --help shows options')
const result = await $`npx paseo send --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'send --help should exit 0')
assert(result.stdout.includes('--no-wait'), 'help should mention --no-wait flag')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('<id>'), 'help should mention id argument')
@@ -45,14 +45,14 @@ try {
console.log(' help should mention --host option')
console.log(' help should mention <id> argument')
console.log(' help should mention <prompt> argument')
console.log('✓ agent send --help shows options\n')
console.log('✓ send --help shows options\n')
}
// Test 2: agent send requires id argument
// Test 2: send requires id argument
{
console.log('Test 2: agent send requires id argument')
console.log('Test 2: send requires id argument')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent send`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo send`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without id')
const output = result.stdout + result.stderr
// Commander should complain about missing argument
@@ -61,14 +61,14 @@ try {
output.toLowerCase().includes('required') ||
output.toLowerCase().includes('argument')
assert(hasMissingArg, 'error should mention missing argument')
console.log('✓ agent send requires id argument\n')
console.log('✓ send requires id argument\n')
}
// Test 3: agent send requires prompt argument
// Test 3: send requires prompt argument
{
console.log('Test 3: agent send requires prompt argument')
console.log('Test 3: send requires prompt argument')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent send abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo send abc123`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without prompt')
const output = result.stdout + result.stderr
// Commander should complain about missing argument
@@ -77,14 +77,14 @@ try {
output.toLowerCase().includes('required') ||
output.toLowerCase().includes('argument')
assert(hasMissingArg, 'error should mention missing argument')
console.log('✓ agent send requires prompt argument\n')
console.log('✓ send requires prompt argument\n')
}
// Test 4: agent send handles daemon not running
// Test 4: send handles daemon not running
{
console.log('Test 4: agent send handles daemon not running')
console.log('Test 4: send handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent send abc123 "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo send abc123 "test prompt"`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
@@ -93,67 +93,67 @@ try {
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ agent send handles daemon not running\n')
console.log('✓ send handles daemon not running\n')
}
// Test 5: agent send --no-wait flag is accepted
// Test 5: send --no-wait flag is accepted
{
console.log('Test 5: agent send --no-wait flag is accepted')
console.log('Test 5: send --no-wait flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent send --no-wait abc123 "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo send --no-wait abc123 "test prompt"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --no-wait flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent send --no-wait flag is accepted\n')
console.log('✓ send --no-wait flag is accepted\n')
}
// Test 6: agent send --host flag is accepted
// Test 6: send --host flag is accepted
{
console.log('Test 6: agent send --host flag is accepted')
console.log('Test 6: send --host flag is accepted')
const result =
await $`PASEO_HOME=${paseoHome} npx paseo agent send --host localhost:${port} abc123 "test prompt"`.nothrow()
await $`PASEO_HOME=${paseoHome} npx paseo send --host localhost:${port} abc123 "test prompt"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --host flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent send --host flag is accepted\n')
console.log('✓ send --host flag is accepted\n')
}
// Test 7: -q (quiet) flag is accepted with agent send
// Test 7: -q (quiet) flag is accepted with send
{
console.log('Test 7: -q (quiet) flag is accepted with agent send')
console.log('Test 7: -q (quiet) flag is accepted with send')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q agent send --no-wait abc123 "test prompt"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q send --no-wait abc123 "test prompt"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ -q (quiet) flag is accepted with agent send\n')
console.log('✓ -q (quiet) flag is accepted with send\n')
}
// Test 8: Combined flags work together
{
console.log('Test 8: Combined flags work together')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q agent send --no-wait abc123 "Run the linter"`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q send --no-wait abc123 "Run the linter"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept all combined flags')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ Combined flags work together\n')
}
// Test 9: agent --help shows send subcommand
// Test 9: paseo --help shows send command
{
console.log('Test 9: agent --help shows send subcommand')
const result = await $`npx paseo agent --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent --help should exit 0')
assert(result.stdout.includes('send'), 'help should mention send subcommand')
console.log('✓ agent --help shows send subcommand\n')
console.log('Test 9: paseo --help shows send command')
const result = await $`npx paseo --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'paseo --help should exit 0')
assert(result.stdout.includes('send'), 'help should mention send command')
console.log('✓ paseo --help shows send command\n')
}
// Test 10: ID prefix syntax is mentioned in help
{
console.log('Test 10: send command description mentions ID')
const result = await $`npx paseo agent send --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent send --help should exit 0')
const result = await $`npx paseo send --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'send --help should exit 0')
const hasIdMention =
result.stdout.toLowerCase().includes('id') ||
result.stdout.toLowerCase().includes('prefix')
@@ -165,4 +165,4 @@ try {
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All agent send tests passed ===')
console.log('=== All send tests passed ===')
+44 -44
View File
@@ -1,20 +1,20 @@
#!/usr/bin/env npx tsx
/**
* Phase 6: Agent Stop Command Tests
* Phase 6: Stop Command Tests
*
* Tests the agent stop command - stopping agents (cancel if running, then terminate).
* Tests the stop command - stopping agents (cancel if running, then terminate) (top-level command).
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - agent stop --help shows options
* - agent stop requires ID, --all, or --cwd
* - agent stop handles daemon not running
* - agent stop --all flag is accepted
* - agent stop --cwd flag is accepted
* - stop --help shows options
* - stop requires ID, --all, or --cwd
* - stop handles daemon not running
* - stop --all flag is accepted
* - stop --cwd flag is accepted
*/
import assert from 'node:assert'
@@ -25,30 +25,30 @@ import { join } from 'path'
$.verbose = false
console.log('=== Agent Stop Command Tests ===\n')
console.log('=== Stop Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: agent stop --help shows options
// Test 1: stop --help shows options
{
console.log('Test 1: agent stop --help shows options')
const result = await $`npx paseo agent stop --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent stop --help should exit 0')
console.log('Test 1: stop --help shows options')
const result = await $`npx paseo stop --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'stop --help should exit 0')
assert(result.stdout.includes('--all'), 'help should mention --all flag')
assert(result.stdout.includes('--cwd'), 'help should mention --cwd option')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('[id]'), 'help should mention optional id argument')
console.log('✓ agent stop --help shows options\n')
console.log('✓ stop --help shows options\n')
}
// Test 2: agent stop requires ID, --all, or --cwd
// Test 2: stop requires ID, --all, or --cwd
{
console.log('Test 2: agent stop requires ID, --all, or --cwd')
console.log('Test 2: stop requires ID, --all, or --cwd')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent stop`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo stop`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without id, --all, or --cwd')
const output = result.stdout + result.stderr
const hasError =
@@ -57,14 +57,14 @@ try {
output.toLowerCase().includes('argument') ||
output.toLowerCase().includes('id')
assert(hasError, 'error should mention missing argument')
console.log('✓ agent stop requires ID, --all, or --cwd\n')
console.log('✓ stop requires ID, --all, or --cwd\n')
}
// Test 3: agent stop handles daemon not running
// Test 3: stop handles daemon not running
{
console.log('Test 3: agent stop handles daemon not running')
console.log('Test 3: stop handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent stop abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo stop abc123`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
@@ -73,64 +73,64 @@ try {
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ agent stop handles daemon not running\n')
console.log('✓ stop handles daemon not running\n')
}
// Test 4: agent stop --all flag is accepted
// Test 4: stop --all flag is accepted
{
console.log('Test 4: agent stop --all flag is accepted')
console.log('Test 4: stop --all flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent stop --all`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo stop --all`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --all flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent stop --all flag is accepted\n')
console.log('✓ stop --all flag is accepted\n')
}
// Test 5: agent stop --cwd flag is accepted
// Test 5: stop --cwd flag is accepted
{
console.log('Test 5: agent stop --cwd flag is accepted')
console.log('Test 5: stop --cwd flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent stop --cwd /tmp`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo stop --cwd /tmp`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --cwd flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent stop --cwd flag is accepted\n')
console.log('✓ stop --cwd flag is accepted\n')
}
// Test 6: agent stop with ID and --host flag is accepted
// Test 6: stop with ID and --host flag is accepted
{
console.log('Test 6: agent stop with ID and --host flag is accepted')
console.log('Test 6: stop with ID and --host flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent stop abc123 --host localhost:${port}`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo stop abc123 --host localhost:${port}`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --host flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent stop with ID and --host flag is accepted\n')
console.log('✓ stop with ID and --host flag is accepted\n')
}
// Test 7: agent shows stop in subcommands
// Test 7: paseo --help shows stop command
{
console.log('Test 7: agent --help shows stop subcommand')
const result = await $`npx paseo agent --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent --help should exit 0')
assert(result.stdout.includes('stop'), 'help should mention stop subcommand')
console.log('✓ agent --help shows stop subcommand\n')
console.log('Test 7: paseo --help shows stop command')
const result = await $`npx paseo --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'paseo --help should exit 0')
assert(result.stdout.includes('stop'), 'help should mention stop command')
console.log('✓ paseo --help shows stop command\n')
}
// Test 8: -q (quiet) flag is accepted with agent stop
// Test 8: -q (quiet) flag is accepted with stop
{
console.log('Test 8: -q (quiet) flag is accepted with agent stop')
console.log('Test 8: -q (quiet) flag is accepted with stop')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q agent stop abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q stop abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ -q (quiet) flag is accepted with agent stop\n')
console.log('✓ -q (quiet) flag is accepted with stop\n')
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All agent stop tests passed ===')
console.log('=== All stop tests passed ===')
+48 -48
View File
@@ -1,20 +1,20 @@
#!/usr/bin/env npx tsx
/**
* Phase 7: Agent Logs Command Tests
* Phase 7: Logs Command Tests
*
* Tests the agent logs command - viewing agent activity/timeline.
* Tests the logs command - viewing agent activity/timeline (top-level command).
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - agent logs --help shows options
* - agent logs requires ID argument
* - agent logs handles daemon not running
* - agent logs -f (follow) flag is accepted
* - agent logs --tail flag is accepted
* - logs --help shows options
* - logs requires ID argument
* - logs handles daemon not running
* - logs -f (follow) flag is accepted
* - logs --tail flag is accepted
*/
import assert from 'node:assert'
@@ -25,30 +25,30 @@ import { join } from 'path'
$.verbose = false
console.log('=== Agent Logs Command Tests ===\n')
console.log('=== Logs Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: agent logs --help shows options
// Test 1: logs --help shows options
{
console.log('Test 1: agent logs --help shows options')
const result = await $`npx paseo agent logs --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent logs --help should exit 0')
console.log('Test 1: logs --help shows options')
const result = await $`npx paseo logs --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'logs --help should exit 0')
assert(result.stdout.includes('-f') || result.stdout.includes('--follow'), 'help should mention -f/--follow flag')
assert(result.stdout.includes('--tail'), 'help should mention --tail option')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('<id>'), 'help should mention required id argument')
console.log('✓ agent logs --help shows options\n')
console.log('✓ logs --help shows options\n')
}
// Test 2: agent logs requires ID argument
// Test 2: logs requires ID argument
{
console.log('Test 2: agent logs requires ID argument')
console.log('Test 2: logs requires ID argument')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent logs`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo logs`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without id')
const output = result.stdout + result.stderr
const hasError =
@@ -57,14 +57,14 @@ try {
output.toLowerCase().includes('argument') ||
output.toLowerCase().includes('id')
assert(hasError, 'error should mention missing argument')
console.log('✓ agent logs requires ID argument\n')
console.log('✓ logs requires ID argument\n')
}
// Test 3: agent logs handles daemon not running
// Test 3: logs handles daemon not running
{
console.log('Test 3: agent logs handles daemon not running')
console.log('Test 3: logs handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent logs abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo logs abc123`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
@@ -73,76 +73,76 @@ try {
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ agent logs handles daemon not running\n')
console.log('✓ logs handles daemon not running\n')
}
// Test 4: agent logs -f (follow) flag is accepted
// Test 4: logs -f (follow) flag is accepted
{
console.log('Test 4: agent logs -f (follow) flag is accepted')
console.log('Test 4: logs -f (follow) flag is accepted')
// Use timeout to avoid hanging on follow mode
const result =
await $`timeout 1 bash -c 'PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent logs -f abc123' || true`.nothrow()
await $`timeout 1 bash -c 'PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo logs -f abc123' || true`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -f flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent logs -f (follow) flag is accepted\n')
console.log('✓ logs -f (follow) flag is accepted\n')
}
// Test 5: agent logs --follow flag is accepted
// Test 5: logs --follow flag is accepted
{
console.log('Test 5: agent logs --follow flag is accepted')
console.log('Test 5: logs --follow flag is accepted')
const result =
await $`timeout 1 bash -c 'PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent logs --follow abc123' || true`.nothrow()
await $`timeout 1 bash -c 'PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo logs --follow abc123' || true`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --follow flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent logs --follow flag is accepted\n')
console.log('✓ logs --follow flag is accepted\n')
}
// Test 6: agent logs --tail flag is accepted
// Test 6: logs --tail flag is accepted
{
console.log('Test 6: agent logs --tail flag is accepted')
console.log('Test 6: logs --tail flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent logs --tail 50 abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo logs --tail 50 abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --tail flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent logs --tail flag is accepted\n')
console.log('✓ logs --tail flag is accepted\n')
}
// Test 7: agent logs with ID and --host flag is accepted
// Test 7: logs with ID and --host flag is accepted
{
console.log('Test 7: agent logs with ID and --host flag is accepted')
console.log('Test 7: logs with ID and --host flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent logs abc123 --host localhost:${port}`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo logs abc123 --host localhost:${port}`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --host flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent logs with ID and --host flag is accepted\n')
console.log('✓ logs with ID and --host flag is accepted\n')
}
// Test 8: agent shows logs in subcommands
// Test 8: paseo --help shows logs command
{
console.log('Test 8: agent --help shows logs subcommand')
const result = await $`npx paseo agent --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent --help should exit 0')
assert(result.stdout.includes('logs'), 'help should mention logs subcommand')
console.log('✓ agent --help shows logs subcommand\n')
console.log('Test 8: paseo --help shows logs command')
const result = await $`npx paseo --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'paseo --help should exit 0')
assert(result.stdout.includes('logs'), 'help should mention logs command')
console.log('✓ paseo --help shows logs command\n')
}
// Test 9: -q (quiet) flag is accepted with agent logs
// Test 9: -q (quiet) flag is accepted with logs
{
console.log('Test 9: -q (quiet) flag is accepted with agent logs')
console.log('Test 9: -q (quiet) flag is accepted with logs')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q agent logs abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q logs abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ -q (quiet) flag is accepted with agent logs\n')
console.log('✓ -q (quiet) flag is accepted with logs\n')
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All agent logs tests passed ===')
console.log('=== All logs tests passed ===')
+46 -46
View File
@@ -1,19 +1,19 @@
#!/usr/bin/env npx tsx
/**
* Phase 9: Agent Inspect Command Tests
* Phase 9: Inspect Command Tests
*
* Tests the agent inspect command - showing detailed agent information.
* Tests the inspect command - showing detailed agent information (top-level command).
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - agent inspect --help shows options
* - agent inspect requires id argument
* - agent inspect handles daemon not running
* - agent inspect --host flag is accepted
* - inspect --help shows options
* - inspect requires id argument
* - inspect handles daemon not running
* - inspect --host flag is accepted
* - agent shows inspect in subcommands
*/
@@ -25,18 +25,18 @@ import { join } from 'path'
$.verbose = false
console.log('=== Agent Inspect Command Tests ===\n')
console.log('=== Inspect Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: agent inspect --help shows options
// Test 1: inspect --help shows options
{
console.log('Test 1: agent inspect --help shows options')
const result = await $`npx paseo agent inspect --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent inspect --help should exit 0')
console.log('Test 1: inspect --help shows options')
const result = await $`npx paseo inspect --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'inspect --help should exit 0')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('<id>'), 'help should mention id argument')
console.log(' help should mention --host option')
@@ -44,11 +44,11 @@ try {
console.log('inspect --help shows options\n')
}
// Test 2: agent inspect requires id argument
// Test 2: inspect requires id argument
{
console.log('Test 2: agent inspect requires id argument')
console.log('Test 2: inspect requires id argument')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent inspect`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo inspect`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without id')
const output = result.stdout + result.stderr
// Commander should complain about missing argument
@@ -57,14 +57,14 @@ try {
output.toLowerCase().includes('required') ||
output.toLowerCase().includes('argument')
assert(hasMissingArg, 'error should mention missing argument')
console.log('agent inspect requires id argument\n')
console.log('inspect requires id argument\n')
}
// Test 3: agent inspect handles daemon not running
// Test 3: inspect handles daemon not running
{
console.log('Test 3: agent inspect handles daemon not running')
console.log('Test 3: inspect handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent inspect abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo inspect abc123`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
@@ -73,67 +73,67 @@ try {
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('agent inspect handles daemon not running\n')
console.log('inspect handles daemon not running\n')
}
// Test 4: agent inspect --host flag is accepted
// Test 4: inspect --host flag is accepted
{
console.log('Test 4: agent inspect --host flag is accepted')
console.log('Test 4: inspect --host flag is accepted')
const result =
await $`PASEO_HOME=${paseoHome} npx paseo agent inspect --host localhost:${port} abc123`.nothrow()
await $`PASEO_HOME=${paseoHome} npx paseo inspect --host localhost:${port} abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --host flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('agent inspect --host flag is accepted\n')
console.log('inspect --host flag is accepted\n')
}
// Test 5: -q (quiet) flag is accepted with agent inspect
// Test 5: -q (quiet) flag is accepted with inspect
{
console.log('Test 5: -q (quiet) flag is accepted with agent inspect')
console.log('Test 5: -q (quiet) flag is accepted with inspect')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q agent inspect abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q inspect abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('-q (quiet) flag is accepted with agent inspect\n')
console.log('-q (quiet) flag is accepted with inspect\n')
}
// Test 6: --format json flag is accepted with agent inspect
// Test 6: --format json flag is accepted with inspect
{
console.log('Test 6: --format json flag is accepted with agent inspect')
console.log('Test 6: --format json flag is accepted with inspect')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo --format json agent inspect abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo --format json inspect abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --format json flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('--format json flag is accepted with agent inspect\n')
console.log('--format json flag is accepted with inspect\n')
}
// Test 7: --format yaml flag is accepted with agent inspect
// Test 7: --format yaml flag is accepted with inspect
{
console.log('Test 7: --format yaml flag is accepted with agent inspect')
console.log('Test 7: --format yaml flag is accepted with inspect')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo --format yaml agent inspect abc123`.nothrow()
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo --format yaml inspect abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --format yaml flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('--format yaml flag is accepted with agent inspect\n')
console.log('--format yaml flag is accepted with inspect\n')
}
// Test 8: agent --help shows inspect subcommand
// Test 8: paseo --help shows inspect command
{
console.log('Test 8: agent --help shows inspect subcommand')
const result = await $`npx paseo agent --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent --help should exit 0')
assert(result.stdout.includes('inspect'), 'help should mention inspect subcommand')
console.log('agent --help shows inspect subcommand\n')
console.log('Test 8: paseo --help shows inspect command')
const result = await $`npx paseo --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'paseo --help should exit 0')
assert(result.stdout.includes('inspect'), 'help should mention inspect command')
console.log('paseo --help shows inspect command\n')
}
// Test 9: inspect command description is helpful
{
console.log('Test 9: inspect command description is helpful')
const result = await $`npx paseo agent inspect --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent inspect --help should exit 0')
const result = await $`npx paseo inspect --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'inspect --help should exit 0')
const hasDescription =
result.stdout.toLowerCase().includes('detail') ||
result.stdout.toLowerCase().includes('information') ||
@@ -145,8 +145,8 @@ try {
// Test 10: ID prefix syntax is mentioned in help
{
console.log('Test 10: inspect command mentions ID')
const result = await $`npx paseo agent inspect --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent inspect --help should exit 0')
const result = await $`npx paseo inspect --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'inspect --help should exit 0')
const hasIdMention =
result.stdout.toLowerCase().includes('id') ||
result.stdout.toLowerCase().includes('prefix')
@@ -158,4 +158,4 @@ try {
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All agent inspect tests passed ===')
console.log('=== All inspect tests passed ===')
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env npx tsx
/**
* Phase 11: Agent Archive Command Tests
*
* Tests the agent archive command - archiving (soft-delete) agents.
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - agent archive --help shows options
* - agent archive requires ID argument
* - agent archive handles daemon not running
* - agent archive --force flag is accepted
*/
import assert from 'node:assert'
import { $ } from 'zx'
import { mkdtemp, rm } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
$.verbose = false
console.log('=== Agent Archive Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: agent archive --help shows options
{
console.log('Test 1: agent archive --help shows options')
const result = await $`npx paseo agent archive --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent archive --help should exit 0')
assert(result.stdout.includes('--force'), 'help should mention --force flag')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('<id>'), 'help should mention required id argument')
console.log('✓ agent archive --help shows options\n')
}
// Test 2: agent archive requires ID argument
{
console.log('Test 2: agent archive requires ID argument')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent archive`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without id')
const output = result.stdout + result.stderr
const hasError =
output.toLowerCase().includes('missing') ||
output.toLowerCase().includes('required') ||
output.toLowerCase().includes('argument')
assert(hasError, 'error should mention missing argument')
console.log('✓ agent archive requires ID argument\n')
}
// Test 3: agent archive handles daemon not running
{
console.log('Test 3: agent archive handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent archive abc123`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
const hasError =
output.toLowerCase().includes('daemon') ||
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ agent archive handles daemon not running\n')
}
// Test 4: agent archive --force flag is accepted
{
console.log('Test 4: agent archive --force flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent archive abc123 --force`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --force flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent archive --force flag is accepted\n')
}
// Test 5: agent archive with ID and --host flag is accepted
{
console.log('Test 5: agent archive with ID and --host flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo agent archive abc123 --host localhost:${port}`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --host flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ agent archive with ID and --host flag is accepted\n')
}
// Test 6: agent shows archive in subcommands
{
console.log('Test 6: agent --help shows archive subcommand')
const result = await $`npx paseo agent --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'agent --help should exit 0')
assert(result.stdout.includes('archive'), 'help should mention archive subcommand')
console.log('✓ agent --help shows archive subcommand\n')
}
// Test 7: -q (quiet) flag is accepted with agent archive
{
console.log('Test 7: -q (quiet) flag is accepted with agent archive')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q agent archive abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ -q (quiet) flag is accepted with agent archive\n')
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All agent archive tests passed ===')
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env npx tsx
/**
* Phase 11: Wait Command Tests
*
* Tests the wait command - waiting for an agent to become idle (top-level command).
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - wait --help shows options
* - wait requires id argument
* - wait handles daemon not running
* - wait --timeout flag is accepted
* - wait --host flag is accepted
* - agent shows wait in subcommands
*/
import assert from 'node:assert'
import { $ } from 'zx'
import { mkdtemp, rm } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
$.verbose = false
console.log('=== Wait Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: wait --help shows options
{
console.log('Test 1: wait --help shows options')
const result = await $`npx paseo wait --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'wait --help should exit 0')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('--timeout'), 'help should mention --timeout option')
assert(result.stdout.includes('<id>'), 'help should mention id argument')
console.log(' help should mention --host option')
console.log(' help should mention --timeout option')
console.log(' help should mention <id> argument')
console.log('wait --help shows options\n')
}
// Test 2: wait requires id argument
{
console.log('Test 2: wait requires id argument')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo wait`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without id')
const output = result.stdout + result.stderr
// Commander should complain about missing argument
const hasMissingArg =
output.toLowerCase().includes('missing') ||
output.toLowerCase().includes('required') ||
output.toLowerCase().includes('argument')
assert(hasMissingArg, 'error should mention missing argument')
console.log('wait requires id argument\n')
}
// Test 3: wait handles daemon not running
{
console.log('Test 3: wait handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo wait abc123`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
const hasError =
output.toLowerCase().includes('daemon') ||
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('wait handles daemon not running\n')
}
// Test 4: wait --timeout flag is accepted
{
console.log('Test 4: wait --timeout flag is accepted')
const result =
await $`PASEO_HOME=${paseoHome} npx paseo wait --timeout 30 --host localhost:${port} abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --timeout flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('wait --timeout flag is accepted\n')
}
// Test 5: wait --host flag is accepted
{
console.log('Test 5: wait --host flag is accepted')
const result =
await $`PASEO_HOME=${paseoHome} npx paseo wait --host localhost:${port} abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --host flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('wait --host flag is accepted\n')
}
// Test 6: -q (quiet) flag is accepted with wait
{
console.log('Test 6: -q (quiet) flag is accepted with wait')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q wait abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('-q (quiet) flag is accepted with wait\n')
}
// Test 7: --format json flag is accepted with wait
{
console.log('Test 7: --format json flag is accepted with wait')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo --format json wait abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --format json flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('--format json flag is accepted with wait\n')
}
// Test 8: --format yaml flag is accepted with wait
{
console.log('Test 8: --format yaml flag is accepted with wait')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo --format yaml wait abc123`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --format yaml flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('--format yaml flag is accepted with wait\n')
}
// Test 9: paseo --help shows wait command
{
console.log('Test 9: paseo --help shows wait command')
const result = await $`npx paseo --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'paseo --help should exit 0')
assert(result.stdout.includes('wait'), 'help should mention wait command')
console.log('paseo --help shows wait command\n')
}
// Test 10: wait command description is helpful
{
console.log('Test 10: wait command description is helpful')
const result = await $`npx paseo wait --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'wait --help should exit 0')
const hasDescription =
result.stdout.toLowerCase().includes('wait') ||
result.stdout.toLowerCase().includes('idle')
assert(hasDescription, 'help should describe what wait does')
console.log('wait command description is helpful\n')
}
// Test 11: ID prefix syntax is mentioned in help
{
console.log('Test 11: wait command mentions ID')
const result = await $`npx paseo wait --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'wait --help should exit 0')
const hasIdMention =
result.stdout.toLowerCase().includes('id') ||
result.stdout.toLowerCase().includes('prefix')
assert(hasIdMention, 'help should mention ID or prefix')
console.log('wait command mentions ID\n')
}
// Test 12: timeout option has default value documented
{
console.log('Test 12: timeout option has default value documented')
const result = await $`npx paseo wait --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'wait --help should exit 0')
const hasDefault =
result.stdout.includes('600') || result.stdout.toLowerCase().includes('default')
assert(hasDefault, 'help should mention timeout default value')
console.log('timeout option has default value documented\n')
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All wait tests passed ===')
+107
View File
@@ -0,0 +1,107 @@
#!/usr/bin/env npx tsx
/**
* Permit LS Command Tests
*
* Tests the permit ls command - listing pending permissions.
* Since daemon may not be running, we test:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - JSON output format
*
* Tests:
* - permit --help shows subcommands
* - permit ls --help shows options
* - permit ls returns error when no daemon
* - permit ls --format json handles errors
*/
import assert from 'node:assert'
import { $ } from 'zx'
import { mkdtemp, rm } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
$.verbose = false
console.log('=== Permit LS Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: permit --help shows subcommands
{
console.log('Test 1: permit --help shows subcommands')
const result = await $`npx paseo permit --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'permit --help should exit 0')
assert(result.stdout.includes('ls'), 'help should mention ls subcommand')
assert(result.stdout.includes('allow'), 'help should mention allow subcommand')
assert(result.stdout.includes('deny'), 'help should mention deny subcommand')
console.log('✓ permit --help shows subcommands\n')
}
// Test 2: permit ls --help shows options
{
console.log('Test 2: permit ls --help shows options')
const result = await $`npx paseo permit ls --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'permit ls --help should exit 0')
assert(result.stdout.includes('--host'), 'help should mention --host option')
console.log('✓ permit ls --help shows options\n')
}
// Test 3: permit ls returns error when no daemon running
{
console.log('Test 3: permit ls handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit ls`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
const hasError =
output.toLowerCase().includes('daemon') ||
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ permit ls handles daemon not running\n')
}
// Test 4: permit ls --format json handles errors
{
console.log('Test 4: permit ls --format json handles errors')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit ls --format json`.nothrow()
// Should still fail (daemon not running)
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
// But output should be valid JSON if present
const output = result.stdout.trim()
if (output.length > 0) {
try {
JSON.parse(output)
console.log('✓ permit ls --format json outputs valid JSON error\n')
} catch {
// Empty or stderr-only output is acceptable
console.log('✓ permit ls --format json handled error (output may be in stderr)\n')
}
} else {
console.log('✓ permit ls --format json handled error gracefully\n')
}
}
// Test 5: -q (quiet) flag is accepted globally
{
console.log('Test 5: -q (quiet) flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q permit ls`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ -q (quiet) flag is accepted\n')
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All permit ls tests passed ===')
@@ -0,0 +1,175 @@
#!/usr/bin/env npx tsx
/**
* Permit Allow/Deny Command Tests
*
* Tests the permit allow and deny commands.
* Since daemon may not be running, we test:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - Flag acceptance
*
* Tests:
* - permit allow --help shows options
* - permit deny --help shows options
* - permit allow handles daemon not running
* - permit deny handles daemon not running
* - permit allow --all flag is accepted
* - permit deny --all flag is accepted
* - permit deny --message flag is accepted
* - permit deny --interrupt flag is accepted
* - permit allow --input flag is accepted
*/
import assert from 'node:assert'
import { $ } from 'zx'
import { mkdtemp, rm } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
$.verbose = false
console.log('=== Permit Allow/Deny Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: permit allow --help shows options
{
console.log('Test 1: permit allow --help shows options')
const result = await $`npx paseo permit allow --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'permit allow --help should exit 0')
assert(result.stdout.includes('--all'), 'help should mention --all flag')
assert(result.stdout.includes('--input'), 'help should mention --input option')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('<agent>'), 'help should mention agent argument')
console.log('✓ permit allow --help shows options\n')
}
// Test 2: permit deny --help shows options
{
console.log('Test 2: permit deny --help shows options')
const result = await $`npx paseo permit deny --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'permit deny --help should exit 0')
assert(result.stdout.includes('--all'), 'help should mention --all flag')
assert(result.stdout.includes('--message'), 'help should mention --message option')
assert(result.stdout.includes('--interrupt'), 'help should mention --interrupt flag')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('<agent>'), 'help should mention agent argument')
console.log('✓ permit deny --help shows options\n')
}
// Test 3: permit allow handles daemon not running
{
console.log('Test 3: permit allow handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit allow abc123 req456`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
const hasError =
output.toLowerCase().includes('daemon') ||
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ permit allow handles daemon not running\n')
}
// Test 4: permit deny handles daemon not running
{
console.log('Test 4: permit deny handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit deny abc123 req456`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
const hasError =
output.toLowerCase().includes('daemon') ||
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ permit deny handles daemon not running\n')
}
// Test 5: permit allow --all flag is accepted
{
console.log('Test 5: permit allow --all flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit allow abc123 --all`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --all flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ permit allow --all flag is accepted\n')
}
// Test 6: permit deny --all flag is accepted
{
console.log('Test 6: permit deny --all flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit deny abc123 --all`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --all flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ permit deny --all flag is accepted\n')
}
// Test 7: permit deny --message flag is accepted
{
console.log('Test 7: permit deny --message flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit deny abc123 req456 --message "Not allowed"`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --message flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ permit deny --message flag is accepted\n')
}
// Test 8: permit deny --interrupt flag is accepted
{
console.log('Test 8: permit deny --interrupt flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit deny abc123 req456 --interrupt`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --interrupt flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ permit deny --interrupt flag is accepted\n')
}
// Test 9: permit allow --input flag is accepted
{
console.log('Test 9: permit allow --input flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit allow abc123 req456 --input '{"key":"value"}'`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --input flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ permit allow --input flag is accepted\n')
}
// Test 10: permit allow without req_id and without --all fails gracefully
{
console.log('Test 10: permit allow requires req_id or --all')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit allow abc123`.nothrow()
// This might fail due to daemon not running first, or due to missing argument
// The important thing is it doesn't crash with an unhandled error
assert.notStrictEqual(result.exitCode, 0, 'should fail without req_id or --all')
console.log('✓ permit allow requires req_id or --all\n')
}
// Test 11: permit deny without req_id and without --all fails gracefully
{
console.log('Test 11: permit deny requires req_id or --all')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo permit deny abc123`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without req_id or --all')
console.log('✓ permit deny requires req_id or --all\n')
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All permit allow/deny tests passed ===')
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/env npx tsx
/**
* Phase 14: Worktree Command Tests
*
* Tests the worktree commands for managing Paseo-managed git worktrees.
* Since daemon may not be running, we test both:
* - Help and argument parsing
* - Graceful error handling when daemon not running
* - All flags are accepted
*
* Tests:
* - worktree --help shows subcommands
* - worktree ls --help shows options
* - worktree ls handles daemon not running
* - worktree archive --help shows options
* - worktree archive requires name argument
* - worktree archive handles daemon not running
*/
import assert from 'node:assert'
import { $ } from 'zx'
import { mkdtemp, rm } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
$.verbose = false
console.log('=== Worktree Command Tests ===\n')
// Get random port that's definitely not in use (never 6767)
const port = 10000 + Math.floor(Math.random() * 50000)
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-test-home-'))
try {
// Test 1: worktree --help shows subcommands
{
console.log('Test 1: worktree --help shows subcommands')
const result = await $`npx paseo worktree --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'worktree --help should exit 0')
assert(result.stdout.includes('ls'), 'help should mention ls subcommand')
assert(result.stdout.includes('archive'), 'help should mention archive subcommand')
console.log('✓ worktree --help shows subcommands\n')
}
// Test 2: worktree ls --help shows options
{
console.log('Test 2: worktree ls --help shows options')
const result = await $`npx paseo worktree ls --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'worktree ls --help should exit 0')
assert(result.stdout.includes('--host'), 'help should mention --host option')
console.log('✓ worktree ls --help shows options\n')
}
// Test 3: worktree ls handles daemon not running
{
console.log('Test 3: worktree ls handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo worktree ls`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
const hasError =
output.toLowerCase().includes('daemon') ||
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ worktree ls handles daemon not running\n')
}
// Test 4: worktree ls with --host flag is accepted
{
console.log('Test 4: worktree ls with --host flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo worktree ls --host localhost:${port}`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --host flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ worktree ls with --host flag is accepted\n')
}
// Test 5: worktree archive --help shows options
{
console.log('Test 5: worktree archive --help shows options')
const result = await $`npx paseo worktree archive --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'worktree archive --help should exit 0')
assert(result.stdout.includes('--host'), 'help should mention --host option')
assert(result.stdout.includes('<name>'), 'help should mention required name argument')
console.log('✓ worktree archive --help shows options\n')
}
// Test 6: worktree archive requires name argument
{
console.log('Test 6: worktree archive requires name argument')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo worktree archive`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail without name')
const output = result.stdout + result.stderr
const hasError =
output.toLowerCase().includes('missing') ||
output.toLowerCase().includes('required') ||
output.toLowerCase().includes('argument')
assert(hasError, 'error should mention missing argument')
console.log('✓ worktree archive requires name argument\n')
}
// Test 7: worktree archive handles daemon not running
{
console.log('Test 7: worktree archive handles daemon not running')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo worktree archive test-worktree`.nothrow()
// Should fail because daemon not running
assert.notStrictEqual(result.exitCode, 0, 'should fail when daemon not running')
const output = result.stdout + result.stderr
const hasError =
output.toLowerCase().includes('daemon') ||
output.toLowerCase().includes('connect') ||
output.toLowerCase().includes('cannot')
assert(hasError, 'error message should mention connection issue')
console.log('✓ worktree archive handles daemon not running\n')
}
// Test 8: worktree archive with name and --host flag is accepted
{
console.log('Test 8: worktree archive with name and --host flag is accepted')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo worktree archive test-worktree --host localhost:${port}`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept --host flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ worktree archive with name and --host flag is accepted\n')
}
// Test 9: -q (quiet) flag is accepted with worktree ls
{
console.log('Test 9: -q (quiet) flag is accepted with worktree ls')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -q worktree ls`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -q flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ -q (quiet) flag is accepted with worktree ls\n')
}
// Test 10: -f json flag is accepted with worktree ls
{
console.log('Test 10: -f json flag is accepted with worktree ls')
const result =
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo -f json worktree ls`.nothrow()
const output = result.stdout + result.stderr
assert(!output.includes('unknown option'), 'should accept -f json flag')
assert(!output.includes('error: option'), 'should not have option parsing error')
console.log('✓ -f json flag is accepted with worktree ls\n')
}
// Test 11: paseo --help shows worktree subcommand
{
console.log('Test 11: paseo --help shows worktree subcommand')
const result = await $`npx paseo --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'paseo --help should exit 0')
assert(result.stdout.includes('worktree'), 'help should mention worktree subcommand')
console.log('✓ paseo --help shows worktree subcommand\n')
}
} finally {
// Clean up temp directory
await rm(paseoHome, { recursive: true, force: true })
}
console.log('=== All worktree tests passed ===')
+147
View File
@@ -0,0 +1,147 @@
#!/usr/bin/env npx tsx
/**
* Phase 15: Provider Command Tests
*
* Tests provider commands for listing providers and models.
* Provider data is static and doesn't require a running daemon.
*
* Tests:
* - provider --help shows subcommands
* - provider ls lists all providers
* - provider ls --format json outputs valid JSON
* - provider ls --quiet outputs provider names only
* - provider models claude lists claude models
* - provider models codex lists codex models
* - provider models opencode lists opencode models
* - provider models unknown fails with error
* - provider models --format json outputs valid JSON
*/
import assert from 'node:assert'
import { $ } from 'zx'
$.verbose = false
console.log('=== Provider Commands ===\n')
// Test 1: provider --help shows subcommands
{
console.log('Test 1: provider --help shows subcommands')
const result = await $`npx paseo provider --help`.nothrow()
assert.strictEqual(result.exitCode, 0, 'provider --help should exit 0')
assert(result.stdout.includes('ls'), 'help should mention ls')
assert(result.stdout.includes('models'), 'help should mention models')
console.log('✓ provider --help shows subcommands\n')
}
// Test 2: provider ls lists all providers
{
console.log('Test 2: provider ls lists all providers')
const result = await $`npx paseo provider ls`.nothrow()
assert.strictEqual(result.exitCode, 0, 'provider ls should exit 0')
assert(result.stdout.includes('claude'), 'output should include claude')
assert(result.stdout.includes('codex'), 'output should include codex')
assert(result.stdout.includes('opencode'), 'output should include opencode')
assert(result.stdout.includes('available'), 'output should show available status')
console.log('✓ provider ls lists all providers\n')
}
// Test 3: provider ls --format json outputs valid JSON
{
console.log('Test 3: provider ls --format json outputs valid JSON')
const result = await $`npx paseo provider ls --format json`.nothrow()
assert.strictEqual(result.exitCode, 0, 'should exit 0')
const data = JSON.parse(result.stdout.trim())
assert(Array.isArray(data), 'output should be an array')
assert.strictEqual(data.length, 3, 'should have 3 providers')
assert(data.some((p: { provider: string }) => p.provider === 'claude'), 'should include claude')
assert(data.some((p: { provider: string }) => p.provider === 'codex'), 'should include codex')
assert(data.some((p: { provider: string }) => p.provider === 'opencode'), 'should include opencode')
console.log('✓ provider ls --format json outputs valid JSON\n')
}
// Test 4: provider ls --quiet outputs provider names only
{
console.log('Test 4: provider ls --quiet outputs provider names only')
const result = await $`npx paseo provider ls --quiet`.nothrow()
assert.strictEqual(result.exitCode, 0, 'should exit 0')
const lines = result.stdout.trim().split('\n')
assert.strictEqual(lines.length, 3, 'should have 3 lines')
assert(lines.includes('claude'), 'should include claude')
assert(lines.includes('codex'), 'should include codex')
assert(lines.includes('opencode'), 'should include opencode')
console.log('✓ provider ls --quiet outputs provider names only\n')
}
// Test 5: provider models claude lists claude models
{
console.log('Test 5: provider models claude lists claude models')
const result = await $`npx paseo provider models claude`.nothrow()
assert.strictEqual(result.exitCode, 0, 'provider models claude should exit 0')
assert(result.stdout.includes('claude-sonnet-4-20250514'), 'output should include claude-sonnet-4')
assert(result.stdout.includes('claude-opus-4-20250514'), 'output should include claude-opus-4')
assert(result.stdout.includes('claude-3-5-haiku-20241022'), 'output should include claude-haiku')
console.log('✓ provider models claude lists claude models\n')
}
// Test 6: provider models codex lists codex models
{
console.log('Test 6: provider models codex lists codex models')
const result = await $`npx paseo provider models codex`.nothrow()
assert.strictEqual(result.exitCode, 0, 'provider models codex should exit 0')
assert(result.stdout.includes('o3-mini'), 'output should include o3-mini')
assert(result.stdout.includes('o4-mini'), 'output should include o4-mini')
console.log('✓ provider models codex lists codex models\n')
}
// Test 7: provider models opencode lists opencode models
{
console.log('Test 7: provider models opencode lists opencode models')
const result = await $`npx paseo provider models opencode`.nothrow()
assert.strictEqual(result.exitCode, 0, 'provider models opencode should exit 0')
// opencode supports both claude and codex models
assert(result.stdout.includes('claude-sonnet-4-20250514'), 'output should include claude models')
assert(result.stdout.includes('o3-mini'), 'output should include codex models')
console.log('✓ provider models opencode lists opencode models\n')
}
// Test 8: provider models unknown fails with error
{
console.log('Test 8: provider models unknown fails with error')
const result = await $`npx paseo provider models unknown`.nothrow()
assert.notStrictEqual(result.exitCode, 0, 'should fail for unknown provider')
const output = result.stdout + result.stderr
assert(
output.toLowerCase().includes('unknown') || output.toLowerCase().includes('provider'),
'error should mention unknown provider'
)
console.log('✓ provider models unknown fails with error\n')
}
// Test 9: provider models --format json outputs valid JSON
{
console.log('Test 9: provider models --format json outputs valid JSON')
const result = await $`npx paseo provider models claude --format json`.nothrow()
assert.strictEqual(result.exitCode, 0, 'should exit 0')
const data = JSON.parse(result.stdout.trim())
assert(Array.isArray(data), 'output should be an array')
assert.strictEqual(data.length, 3, 'should have 3 models for claude')
assert(data.every((m: { model: string; id: string }) => m.model && m.id), 'each model should have name and id')
console.log('✓ provider models --format json outputs valid JSON\n')
}
// Test 10: provider models --quiet outputs model IDs only
{
console.log('Test 10: provider models --quiet outputs model IDs only')
const result = await $`npx paseo provider models claude --quiet`.nothrow()
assert.strictEqual(result.exitCode, 0, 'should exit 0')
const lines = result.stdout.trim().split('\n')
assert.strictEqual(lines.length, 3, 'should have 3 lines')
assert(lines.includes('claude-sonnet-4-20250514'), 'should include claude-sonnet-4')
assert(lines.includes('claude-opus-4-20250514'), 'should include claude-opus-4')
assert(lines.includes('claude-3-5-haiku-20241022'), 'should include claude-haiku')
console.log('✓ provider models --quiet outputs model IDs only\n')
}
console.log('=== All provider tests passed ===')
@@ -0,0 +1,246 @@
#!/usr/bin/env npx tsx
/**
* E2E Test: Agent Lifecycle
*
* This test verifies the complete agent lifecycle using REAL daemons and agents.
* It starts an isolated daemon on a random port and runs actual CLI commands.
*
* Test flow:
* 1. Start daemon on random port
* 2. Create agent with `paseo run "say hello" --provider claude`
* 3. List agents with `paseo ls`
* 4. Wait for agent with `paseo wait <id>`
* 5. Inspect agent with `paseo inspect <id>`
* 6. Stop agent with `paseo stop <id>`
* 7. Cleanup: stop daemon, remove temp dirs
*
* CRITICAL RULES:
* - NEVER use port 6767 (user's running daemon)
* - Always use claude provider with haiku model for fast, cheap tests
* - Clean up resources after test completes
*/
import assert from 'node:assert'
import { createE2ETestContext, type TestDaemonContext } from '../helpers/test-daemon.ts'
interface E2EContext extends TestDaemonContext {
paseo: (args: string[], opts?: { timeout?: number; cwd?: string }) => Promise<{
exitCode: number
stdout: string
stderr: string
}>
}
let ctx: E2EContext
async function setup(): Promise<void> {
console.log('Setting up E2E test context...')
console.log('Starting test daemon on random port (this may take a few seconds)...')
try {
ctx = await createE2ETestContext({ timeout: 45000 })
console.log(`Test daemon started on port ${ctx.port}`)
console.log(`PASEO_HOME: ${ctx.paseoHome}`)
console.log(`Work directory: ${ctx.workDir}`)
} catch (err) {
console.error('Failed to start test daemon:', err)
throw err
}
}
async function cleanup(): Promise<void> {
console.log('\nCleaning up...')
if (ctx) {
await ctx.stop()
console.log('Test daemon stopped and temp directories removed')
}
}
async function test_agent_ls_empty(): Promise<void> {
console.log('\n--- Test: agent ls with empty list ---')
const result = await ctx.paseo(['ls', '--format', 'json'])
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent ls should succeed')
const agents = JSON.parse(result.stdout.trim())
assert(Array.isArray(agents), 'Output should be JSON array')
assert.strictEqual(agents.length, 0, 'Should have no agents initially')
console.log('PASS: agent ls returns empty list')
}
async function test_agent_run_detached(): Promise<string> {
console.log('\n--- Test: agent run detached ---')
// Use quiet mode to get just the agent ID
// CRITICAL: Use haiku model for fast, cheap tests
// CRITICAL: Use bypassPermissions mode so agent doesn't wait for permission approvals
const result = await ctx.paseo(
[
'-q',
'run',
'-d',
'--provider',
'claude',
'--model',
'claude-3-5-haiku-20241022',
'--mode',
'bypassPermissions',
'--name',
'E2E Test Agent',
'Say hello world',
],
{ timeout: 60000 }
)
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent run should succeed')
const agentId = result.stdout.trim()
assert(agentId.length > 0, 'Should return agent ID')
assert(agentId.match(/^[a-z0-9-]+$/), `Agent ID should be alphanumeric: ${agentId}`)
console.log(`PASS: agent created with ID: ${agentId}`)
return agentId
}
async function test_agent_ls_shows_agent(agentId: string): Promise<void> {
console.log('\n--- Test: agent ls shows created agent ---')
const result = await ctx.paseo(['ls', '--format', 'json'])
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
assert.strictEqual(result.exitCode, 0, 'agent ls should succeed')
const agents = JSON.parse(result.stdout.trim()) as Array<{ id: string; title?: string; status?: string }>
assert(Array.isArray(agents), 'Output should be JSON array')
assert(agents.length >= 1, 'Should have at least one agent')
// Find our agent by ID prefix
const ourAgent = agents.find((a) => agentId.startsWith(a.id) || a.id.startsWith(agentId.slice(0, 7)))
assert(ourAgent, `Our agent ${agentId} should be in the list`)
console.log('Agent found:', ourAgent)
console.log('PASS: agent ls shows created agent')
}
async function test_agent_wait(agentId: string): Promise<void> {
console.log('\n--- Test: agent wait ---')
// Wait for agent to become idle (with generous timeout for haiku model)
const result = await ctx.paseo(['wait', '--timeout', '120s', agentId], { timeout: 130000 })
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent wait should succeed')
console.log('PASS: agent wait completed successfully')
}
async function test_agent_inspect(agentId: string): Promise<void> {
console.log('\n--- Test: agent inspect ---')
const result = await ctx.paseo(['inspect', agentId])
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
assert.strictEqual(result.exitCode, 0, 'agent inspect should succeed')
// Check that output contains expected fields (table format)
const output = result.stdout
assert(output.includes('Id'), 'Output should contain Id field')
assert(output.includes('Provider'), 'Output should contain Provider field')
assert(output.includes('Status'), 'Output should contain Status field')
assert(output.includes('claude'), 'Output should mention claude provider')
console.log('PASS: agent inspect shows expected fields')
}
async function test_agent_logs(agentId: string): Promise<void> {
console.log('\n--- Test: agent logs ---')
const result = await ctx.paseo(['logs', '--tail', '20', agentId])
console.log('Exit code:', result.exitCode)
console.log('Stdout length:', result.stdout.length)
// Don't print full logs as they can be verbose
assert.strictEqual(result.exitCode, 0, 'agent logs should succeed')
// Logs should have some content (agent was asked to say hello)
assert(result.stdout.length > 0, 'Logs should have some content')
console.log('PASS: agent logs returns content')
}
async function test_agent_stop(agentId: string): Promise<void> {
console.log('\n--- Test: agent stop ---')
const result = await ctx.paseo(['stop', agentId])
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent stop should succeed')
// Verify agent is no longer in running list
const psResult = await ctx.paseo(['ls', '--format', 'json'])
const agents = JSON.parse(psResult.stdout.trim()) as Array<{ id: string; status?: string }>
// Agent might still be in list but should not be running
const ourAgent = agents.find((a) => agentId.startsWith(a.id) || a.id.startsWith(agentId.slice(0, 7)))
if (ourAgent) {
assert(ourAgent.status !== 'running', 'Agent should not be running after stop')
}
console.log('PASS: agent stop completed successfully')
}
async function main(): Promise<void> {
console.log('=== E2E Test: Agent Lifecycle ===\n')
console.log('This test creates REAL agents with REAL daemons.')
console.log('It may take some time as it waits for agent responses.\n')
try {
await setup()
// Run tests in sequence
await test_agent_ls_empty()
const agentId = await test_agent_run_detached()
await test_agent_ls_shows_agent(agentId)
await test_agent_wait(agentId)
await test_agent_inspect(agentId)
await test_agent_logs(agentId)
await test_agent_stop(agentId)
console.log('\n=== All E2E tests passed! ===')
} catch (err) {
console.error('\n=== E2E test FAILED ===')
console.error(err)
process.exitCode = 1
} finally {
await cleanup()
}
}
main()
+205
View File
@@ -0,0 +1,205 @@
#!/usr/bin/env npx tsx
/**
* E2E Test: Agent Send Command
*
* This test verifies the `send` command using REAL daemons and agents.
* It starts an isolated daemon on a random port and runs actual CLI commands.
*
* Test flow:
* 1. Start daemon on random port
* 2. Create agent with detached mode
* 3. Wait for agent to become idle (initial task complete)
* 4. Send new message with `send`
* 5. Wait for agent again
* 6. Verify agent processed the message (check logs)
* 7. Cleanup
*
* CRITICAL RULES:
* - NEVER use port 6767 (user's running daemon)
* - Always use claude provider with haiku model for fast, cheap tests
* - Clean up resources after test completes
*/
import assert from 'node:assert'
import { createE2ETestContext, type TestDaemonContext } from '../helpers/test-daemon.ts'
interface E2EContext extends TestDaemonContext {
paseo: (args: string[], opts?: { timeout?: number; cwd?: string }) => Promise<{
exitCode: number
stdout: string
stderr: string
}>
}
let ctx: E2EContext
async function setup(): Promise<void> {
console.log('Setting up E2E test context...')
console.log('Starting test daemon on random port (this may take a few seconds)...')
try {
ctx = await createE2ETestContext({ timeout: 45000 })
console.log(`Test daemon started on port ${ctx.port}`)
console.log(`PASEO_HOME: ${ctx.paseoHome}`)
console.log(`Work directory: ${ctx.workDir}`)
} catch (err) {
console.error('Failed to start test daemon:', err)
throw err
}
}
async function cleanup(): Promise<void> {
console.log('\nCleaning up...')
if (ctx) {
await ctx.stop()
console.log('Test daemon stopped and temp directories removed')
}
}
async function test_create_agent(): Promise<string> {
console.log('\n--- Test: Create agent in detached mode ---')
// CRITICAL: Use haiku model for fast, cheap tests
// CRITICAL: Use bypassPermissions mode so agent doesn't wait for permission approvals
const result = await ctx.paseo(
[
'-q',
'run',
'-d',
'--provider',
'claude',
'--model',
'claude-3-5-haiku-20241022',
'--mode',
'bypassPermissions',
'--name',
'Send Test Agent',
'Say "initial task complete"',
],
{ timeout: 60000 }
)
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent run should succeed')
const agentId = result.stdout.trim()
assert(agentId.length > 0, 'Should return agent ID')
assert(agentId.match(/^[a-z0-9-]+$/), `Agent ID should be alphanumeric: ${agentId}`)
console.log(`PASS: agent created with ID: ${agentId}`)
return agentId
}
async function test_wait_for_initial_task(agentId: string): Promise<void> {
console.log('\n--- Test: Wait for initial task to complete ---')
const result = await ctx.paseo(['wait', '--timeout', '120s', agentId], { timeout: 130000 })
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent wait should succeed')
console.log('PASS: Initial task completed')
}
async function test_agent_send(agentId: string): Promise<void> {
console.log('\n--- Test: Send follow-up message ---')
// Send a follow-up message to the agent
const result = await ctx.paseo(
['send', agentId, 'Now say "follow-up task complete"'],
{ timeout: 180000 }
)
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent send should succeed')
// Verify output contains expected status
assert(
result.stdout.includes('completed') || result.stdout.includes('sent'),
'Should indicate message was sent or completed'
)
console.log('PASS: Follow-up message sent successfully')
}
async function test_verify_logs(agentId: string): Promise<void> {
console.log('\n--- Test: Verify agent processed both messages ---')
const result = await ctx.paseo(['logs', '--tail', '50', agentId])
console.log('Exit code:', result.exitCode)
console.log('Stdout length:', result.stdout.length)
assert.strictEqual(result.exitCode, 0, 'agent logs should succeed')
// Logs should have content from both tasks
assert(result.stdout.length > 0, 'Logs should have content')
// The agent was asked to say specific things, check if either appears in logs
// This is a loose check since exact log format may vary
const logsLower = result.stdout.toLowerCase()
const hasInitialTask = logsLower.includes('initial') || logsLower.includes('hello') || logsLower.includes('task')
const hasFollowUp = logsLower.includes('follow') || logsLower.includes('complete') || logsLower.includes('task')
// At minimum, there should be log entries (we can't guarantee exact content)
assert(result.stdout.split('\n').length > 3, 'Should have multiple log entries from both tasks')
console.log('PASS: Agent logs show activity from both tasks')
}
async function test_agent_stop(agentId: string): Promise<void> {
console.log('\n--- Test: Stop agent ---')
const result = await ctx.paseo(['stop', agentId])
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent stop should succeed')
console.log('PASS: Agent stopped successfully')
}
async function main(): Promise<void> {
console.log('=== E2E Test: Agent Send Command ===\n')
console.log('This test verifies the `send` command with REAL agents.')
console.log('It may take some time as it waits for agent responses.\n')
try {
await setup()
// Create agent and wait for initial task
const agentId = await test_create_agent()
await test_wait_for_initial_task(agentId)
// Send follow-up message (this waits for completion by default)
await test_agent_send(agentId)
// Verify both messages were processed
await test_verify_logs(agentId)
// Cleanup agent
await test_agent_stop(agentId)
console.log('\n=== All agent-send E2E tests passed! ===')
} catch (err) {
console.error('\n=== E2E test FAILED ===')
console.error(err)
process.exitCode = 1
} finally {
await cleanup()
}
}
main()
+285
View File
@@ -0,0 +1,285 @@
#!/usr/bin/env npx tsx
/**
* E2E Test: Permissions Workflow
*
* This test verifies the permissions workflow using REAL daemons and agents.
* It starts an isolated daemon on a random port and runs actual CLI commands.
*
* Test flow:
* 1. Start daemon on random port
* 2. Create agent in DEFAULT mode (not bypassPermissions) so it requests permissions
* 3. Wait for agent to hit a permission request
* 4. Use `permit ls` to list pending permissions
* 5. Use `permit allow` to approve the permission
* 6. Verify agent continues after approval
* 7. Cleanup
*
* CRITICAL RULES:
* - NEVER use port 6767 (user's running daemon)
* - Always use claude provider with haiku model for fast, cheap tests
* - Clean up resources after test completes
*/
import assert from 'node:assert'
import { createE2ETestContext, type TestDaemonContext } from '../helpers/test-daemon.ts'
interface E2EContext extends TestDaemonContext {
paseo: (args: string[], opts?: { timeout?: number; cwd?: string }) => Promise<{
exitCode: number
stdout: string
stderr: string
}>
}
let ctx: E2EContext
async function setup(): Promise<void> {
console.log('Setting up E2E test context...')
console.log('Starting test daemon on random port (this may take a few seconds)...')
try {
ctx = await createE2ETestContext({ timeout: 45000 })
console.log(`Test daemon started on port ${ctx.port}`)
console.log(`PASEO_HOME: ${ctx.paseoHome}`)
console.log(`Work directory: ${ctx.workDir}`)
} catch (err) {
console.error('Failed to start test daemon:', err)
throw err
}
}
async function cleanup(): Promise<void> {
console.log('\nCleaning up...')
if (ctx) {
await ctx.stop()
console.log('Test daemon stopped and temp directories removed')
}
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
async function test_create_agent_with_permissions(): Promise<string> {
console.log('\n--- Test: Create agent in default mode (will request permissions) ---')
// Create agent WITHOUT bypassPermissions - it will need to request permissions
// Use a task that is very likely to trigger a tool use (and thus permission request)
const result = await ctx.paseo(
[
'-q',
'agent',
'run',
'-d',
'--provider',
'claude',
'--model',
'claude-3-5-haiku-20241022',
'--name',
'Permission Test Agent',
// This prompt should trigger file read or bash command, requiring permission
'List the files in the current directory using ls',
],
{ timeout: 60000 }
)
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent run should succeed')
const agentId = result.stdout.trim()
assert(agentId.length > 0, 'Should return agent ID')
assert(agentId.match(/^[a-z0-9-]+$/), `Agent ID should be alphanumeric: ${agentId}`)
console.log(`PASS: agent created with ID: ${agentId}`)
return agentId
}
async function test_wait_for_permission_request(agentId: string): Promise<void> {
console.log('\n--- Test: Wait for agent to request permission ---')
// Poll for permission requests with timeout
const maxWait = 60000 // 60 seconds max
const pollInterval = 1000 // 1 second
const startTime = Date.now()
while (Date.now() - startTime < maxWait) {
const result = await ctx.paseo(['permit', 'ls', '--format', 'json'])
if (result.exitCode === 0) {
try {
const permissions = JSON.parse(result.stdout.trim())
if (Array.isArray(permissions) && permissions.length > 0) {
// Check if any permission is for our agent
const ourPermission = permissions.find(
(p: { agentId?: string }) => p.agentId?.startsWith(agentId.slice(0, 7)) || agentId.startsWith(p.agentId?.slice(0, 7) || '')
)
if (ourPermission) {
console.log('Permission request detected:', ourPermission)
console.log('PASS: Agent requested permission')
return
}
}
} catch {
// JSON parse failed, continue polling
}
}
await sleep(pollInterval)
}
// If we get here, check agent status - it might have already completed
const statusResult = await ctx.paseo(['inspect', agentId])
console.log('Agent status:', statusResult.stdout)
// It's possible the agent completed without needing permissions (e.g., haiku might not use tools)
// In that case, we should skip the permission tests
if (statusResult.stdout.includes('idle') || statusResult.stdout.includes('completed')) {
console.log('SKIP: Agent completed without requiring permissions (model chose not to use tools)')
throw new Error('SKIP_PERMISSION_TEST')
}
throw new Error('Timeout waiting for agent to request permission')
}
async function test_permit_ls(): Promise<{ agentShortId: string; requestId: string }> {
console.log('\n--- Test: List pending permissions with permit ls ---')
const result = await ctx.paseo(['permit', 'ls', '--format', 'json'])
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'permit ls should succeed')
const permissions = JSON.parse(result.stdout.trim())
assert(Array.isArray(permissions), 'Output should be JSON array')
assert(permissions.length > 0, 'Should have at least one pending permission')
const permission = permissions[0]
assert(permission.id, 'Permission should have id')
assert(permission.agentShortId, 'Permission should have agentShortId')
assert(permission.name, 'Permission should have tool name')
console.log(`PASS: Found ${permissions.length} pending permission(s)`)
console.log(` Request ID: ${permission.id}`)
console.log(` Agent: ${permission.agentShortId}`)
console.log(` Tool: ${permission.name}`)
return {
agentShortId: permission.agentShortId,
requestId: permission.id,
}
}
async function test_permit_allow(agentShortId: string, requestId: string): Promise<void> {
console.log('\n--- Test: Allow permission with permit allow ---')
const result = await ctx.paseo(['permit', 'allow', agentShortId, requestId])
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'permit allow should succeed')
// Check that output shows the permission was allowed
const output = result.stdout.toLowerCase()
assert(output.includes('allowed') || output.includes(requestId.slice(0, 8).toLowerCase()), 'Should confirm permission was allowed')
console.log('PASS: Permission allowed successfully')
}
async function test_agent_continues(agentId: string): Promise<void> {
console.log('\n--- Test: Verify agent continues after permission granted ---')
// Wait for agent to become idle (should continue after permission)
const result = await ctx.paseo(['wait', '--timeout', '120s', agentId], { timeout: 130000 })
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent wait should succeed after permission granted')
// Verify agent status
const inspectResult = await ctx.paseo(['inspect', agentId])
console.log('Final agent status:', inspectResult.stdout)
assert(inspectResult.stdout.includes('idle') || inspectResult.stdout.includes('completed'), 'Agent should be idle after completing')
console.log('PASS: Agent completed after permission was granted')
}
async function test_agent_stop(agentId: string): Promise<void> {
console.log('\n--- Test: Stop agent ---')
const result = await ctx.paseo(['stop', agentId])
console.log('Exit code:', result.exitCode)
console.log('Stdout:', result.stdout)
if (result.stderr) console.log('Stderr:', result.stderr)
assert.strictEqual(result.exitCode, 0, 'agent stop should succeed')
console.log('PASS: Agent stopped successfully')
}
async function main(): Promise<void> {
console.log('=== E2E Test: Permissions Workflow ===\n')
console.log('This test verifies the permission request/allow workflow.')
console.log('It creates an agent that will request tool permissions.\n')
let agentId: string | undefined
try {
await setup()
// Create agent that will need permissions
agentId = await test_create_agent_with_permissions()
try {
// Wait for permission request
await test_wait_for_permission_request(agentId)
// List and verify permissions
const { agentShortId, requestId } = await test_permit_ls()
// Allow the permission
await test_permit_allow(agentShortId, requestId)
// Verify agent continues and completes
await test_agent_continues(agentId)
console.log('\n=== All permissions E2E tests passed! ===')
} catch (err) {
if (err instanceof Error && err.message === 'SKIP_PERMISSION_TEST') {
console.log('\n=== Permissions test skipped (agent completed without tool use) ===')
console.log('This can happen when the model chooses not to use tools.')
// Still considered a pass - the CLI works, just the model behavior varies
} else {
throw err
}
}
} catch (err) {
console.error('\n=== E2E test FAILED ===')
console.error(err)
process.exitCode = 1
} finally {
// Always try to stop the agent if it was created
if (agentId) {
try {
await test_agent_stop(agentId)
} catch {
// Ignore errors during cleanup
}
}
await cleanup()
}
}
main()
+315
View File
@@ -0,0 +1,315 @@
/**
* Test Daemon Helper
*
* Provides utilities for launching real Paseo daemons in E2E tests.
* Each test gets an isolated daemon on a random port with its own PASEO_HOME.
*
* CRITICAL RULES (from design doc):
* 1. Port: Random port in 20000-30000 range - NEVER use 6767 (production)
* 2. Protocol: WebSocket ONLY - daemon has no HTTP endpoints
* 3. Temp dirs: Create temp directories for PASEO_HOME and agent --cwd
* 4. Model: Always use claude provider with haiku model for fast, cheap tests
* 5. Cleanup: Kill daemon and remove temp dirs after each test
*/
import { mkdtemp, rm, mkdir } from 'fs/promises'
import { existsSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { ChildProcess, spawn } from 'child_process'
export interface TestDaemonContext {
/** Random port for test daemon (never 6767) */
port: number
/** WebSocket URL for connecting to daemon */
wsUrl: string
/** Temp directory for PASEO_HOME */
paseoHome: string
/** Temp directory for agent working directory */
workDir: string
/** Running daemon process */
process: ChildProcess | null
/** Whether the daemon is ready to accept connections */
isReady: boolean
/** Stop the daemon and clean up resources */
stop: () => Promise<void>
}
/**
* Generate a random port for test daemon
* Uses range 20000-30000 to avoid conflicts
* NEVER uses 6767 (user's running daemon)
*/
export function getRandomPort(): number {
return 20000 + Math.floor(Math.random() * 10000)
}
/**
* Create isolated temp directories for testing
*/
export async function createTempDirs(): Promise<{ paseoHome: string; workDir: string }> {
const paseoHome = await mkdtemp(join(tmpdir(), 'paseo-e2e-home-'))
const workDir = await mkdtemp(join(tmpdir(), 'paseo-e2e-work-'))
// Create the agents directory that the daemon expects
const agentsDir = join(paseoHome, 'agents')
await mkdir(agentsDir, { recursive: true })
return { paseoHome, workDir }
}
/**
* Wait for daemon to be ready by running `paseo agent ls`
* This connects via WebSocket and ensures the daemon is responsive
*/
async function waitForDaemonReady(
port: number,
timeout = 30000
): Promise<void> {
const start = Date.now()
while (Date.now() - start < timeout) {
try {
const { exitCode } = await runPaseoCli(
{
port,
wsUrl: `ws://127.0.0.1:${port}`,
paseoHome: '',
workDir: '',
process: null,
isReady: false,
stop: async () => {}
},
['agent', 'ls']
)
if (exitCode === 0) {
return // Daemon is ready
}
} catch {
// Connection failed, keep trying
}
await sleep(100)
}
throw new Error(`Daemon failed to become ready on port ${port} within ${timeout}ms`)
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
* Start a test daemon programmatically using the server's bootstrap API
*
* This starts the daemon in a separate process using the CLI's daemon start command
* with isolated PASEO_HOME and PASEO_PORT environment variables.
*/
export async function startTestDaemon(options?: {
port?: number
paseoHome?: string
workDir?: string
timeout?: number
}): Promise<TestDaemonContext> {
const port = options?.port ?? getRandomPort()
const { paseoHome, workDir } = options?.paseoHome && options?.workDir
? { paseoHome: options.paseoHome, workDir: options.workDir }
: await createTempDirs()
const timeout = options?.timeout ?? 30000
const wsUrl = `ws://127.0.0.1:${port}`
// Find the CLI entry point - use the source file directly with tsx
const cliDir = join(import.meta.dirname, '..', '..')
const cliSrcPath = join(cliDir, 'src', 'index.ts')
// Start daemon process using tsx to run TypeScript directly
const daemonProcess = spawn('npx', ['tsx', cliSrcPath, 'daemon', 'start', '--foreground'], {
env: {
...process.env,
PASEO_HOME: paseoHome,
PASEO_LISTEN: `127.0.0.1:${port}`,
PASEO_PORT: String(port),
// Disable relay for tests
PASEO_RELAY_ENABLED: 'false',
// Force no TTY to prevent QR code output
CI: 'true',
},
stdio: ['ignore', 'pipe', 'pipe'],
detached: false,
})
let stdout = ''
let stderr = ''
daemonProcess.stdout?.on('data', (data) => {
stdout += data.toString()
})
daemonProcess.stderr?.on('data', (data) => {
stderr += data.toString()
})
const cleanup = async () => {
if (daemonProcess && !daemonProcess.killed) {
daemonProcess.kill('SIGTERM')
// Wait for process to exit
await new Promise<void>((resolve) => {
const timeoutId = setTimeout(() => {
daemonProcess.kill('SIGKILL')
resolve()
}, 5000)
daemonProcess.on('exit', () => {
clearTimeout(timeoutId)
resolve()
})
})
}
// Clean up temp directories
try {
if (existsSync(paseoHome)) {
await rm(paseoHome, { recursive: true, force: true })
}
} catch {
// Ignore cleanup errors
}
try {
if (existsSync(workDir)) {
await rm(workDir, { recursive: true, force: true })
}
} catch {
// Ignore cleanup errors
}
}
// Handle process errors
daemonProcess.on('error', (err) => {
console.error('Daemon process error:', err)
})
daemonProcess.on('exit', (code) => {
if (code !== 0 && code !== null) {
console.error(`Daemon process exited with code ${code}`)
if (stderr) {
console.error('Daemon stderr:', stderr)
}
}
})
const ctx: TestDaemonContext = {
port,
wsUrl,
paseoHome,
workDir,
process: daemonProcess,
isReady: false,
stop: cleanup,
}
// Wait for daemon to be ready
try {
await waitForDaemonReady(port, timeout)
ctx.isReady = true
} catch (err) {
// Daemon failed to start - clean up and rethrow
await cleanup()
const message = err instanceof Error ? err.message : String(err)
throw new Error(`Failed to start test daemon: ${message}\nStdout: ${stdout}\nStderr: ${stderr}`)
}
return ctx
}
/**
* Run a paseo CLI command against a test daemon
*
* This is a helper that sets the correct environment variables
* to point at the test daemon.
*/
export async function runPaseoCli(
ctx: TestDaemonContext,
args: string[],
options?: {
timeout?: number
cwd?: string
}
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
const timeout = options?.timeout ?? 60000
const cwd = options?.cwd ?? ctx.workDir
const cliDir = join(import.meta.dirname, '..', '..')
const cliSrcPath = join(cliDir, 'src', 'index.ts')
return new Promise((resolve, reject) => {
const proc = spawn('npx', ['tsx', cliSrcPath, ...args], {
env: {
...process.env,
PASEO_HOST: `localhost:${ctx.port}`,
PASEO_HOME: ctx.paseoHome,
},
cwd,
stdio: ['ignore', 'pipe', 'pipe'],
})
let stdout = ''
let stderr = ''
proc.stdout?.on('data', (data) => {
stdout += data.toString()
})
proc.stderr?.on('data', (data) => {
stderr += data.toString()
})
const timeoutId = setTimeout(() => {
proc.kill('SIGKILL')
reject(new Error(`CLI command timed out after ${timeout}ms: paseo ${args.join(' ')}`))
}, timeout)
proc.on('exit', (code) => {
clearTimeout(timeoutId)
resolve({
exitCode: code ?? 1,
stdout,
stderr,
})
})
proc.on('error', (err) => {
clearTimeout(timeoutId)
reject(err)
})
})
}
/**
* Create a test context that includes a started daemon
* and a helper to run CLI commands against it.
*
* This is the main entry point for E2E tests.
*/
export async function createE2ETestContext(options?: {
timeout?: number
}): Promise<
TestDaemonContext & {
/** Run a paseo CLI command against this daemon */
paseo: (args: string[], opts?: { timeout?: number; cwd?: string }) => Promise<{
exitCode: number
stdout: string
stderr: string
}>
}
> {
const ctx = await startTestDaemon({ timeout: options?.timeout })
const paseo = (args: string[], opts?: { timeout?: number; cwd?: string }) =>
runPaseoCli(ctx, args, opts)
return {
...ctx,
paseo,
}
}
+2 -2
View File
@@ -48,13 +48,13 @@ export async function createTempDirs(): Promise<{ paseoHome: string; workDir: st
/**
* Wait for daemon to be ready by testing WebSocket connection
* Uses `paseo agent ps` which connects via WebSocket
* Uses `paseo agent ls` which connects via WebSocket
*/
export async function waitForDaemon(port: number, timeout = 30000): Promise<void> {
const start = Date.now()
while (Date.now() - start < timeout) {
try {
const result = await $`PASEO_HOST=localhost:${port} paseo agent ps`.nothrow()
const result = await $`PASEO_HOST=localhost:${port} paseo agent ls`.nothrow()
if (result.exitCode === 0) return
} catch {
// Connection failed, keep trying