feat: add --prompt and --prompt-file options to agent send command
This commit is contained in:
@@ -133,7 +133,9 @@ export function createCli(): Command {
|
||||
.command('send')
|
||||
.description('Send a message/task to an existing agent')
|
||||
.argument('<id>', 'Agent ID (or prefix)')
|
||||
.argument('<prompt>', 'The message to send')
|
||||
.argument('[prompt]', 'The message to send')
|
||||
.option('--prompt <text>', 'Provide the message inline as a flag')
|
||||
.option('--prompt-file <path>', 'Read the message from a UTF-8 text file')
|
||||
.option('--no-wait', 'Return immediately without waiting for completion')
|
||||
.option('--image <path>', 'Attach image(s) to the message', collectMultiple, [])
|
||||
).action(withOutput(runSendCommand))
|
||||
|
||||
@@ -88,7 +88,9 @@ export function createAgentCommand(): Command {
|
||||
.command('send')
|
||||
.description('Send a message/task to an existing agent')
|
||||
.argument('<id>', 'Agent ID (or prefix)')
|
||||
.argument('<prompt>', 'The message to send')
|
||||
.argument('[prompt]', 'The message to send')
|
||||
.option('--prompt <text>', 'Provide the message inline as a flag')
|
||||
.option('--prompt-file <path>', 'Read the message from a UTF-8 text file')
|
||||
.option('--no-wait', 'Return immediately without waiting for completion')
|
||||
).action(withOutput(runSendCommand))
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Command } from 'commander'
|
||||
import { connectToDaemon, getDaemonHost } from '../../utils/client.js'
|
||||
import type { CommandOptions, SingleResult, OutputSchema, CommandError } from '../../output/index.js'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { extname } from 'node:path'
|
||||
import { extname, resolve } from 'node:path'
|
||||
|
||||
/** Result type for agent send command */
|
||||
export interface AgentSendResult {
|
||||
@@ -24,6 +24,8 @@ export const agentSendSchema: OutputSchema<AgentSendResult> = {
|
||||
export interface AgentSendOptions extends CommandOptions {
|
||||
noWait?: boolean
|
||||
image?: string[]
|
||||
prompt?: string
|
||||
promptFile?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,9 +79,57 @@ async function readImageFiles(imagePaths: string[]): Promise<Array<{ data: strin
|
||||
return images
|
||||
}
|
||||
|
||||
async function resolvePromptInput(options: {
|
||||
promptArgument: string | undefined
|
||||
promptOption: string | undefined
|
||||
promptFile: string | undefined
|
||||
}): Promise<string> {
|
||||
const promptText = options.promptArgument?.trim()
|
||||
const promptOptionText = options.promptOption?.trim()
|
||||
const promptFilePath = options.promptFile?.trim()
|
||||
const providedSourceCount = [promptText, promptOptionText, promptFilePath].filter(Boolean).length
|
||||
|
||||
if (providedSourceCount > 1) {
|
||||
const error: CommandError = {
|
||||
code: 'CONFLICTING_PROMPT_INPUT',
|
||||
message: 'Provide exactly one of prompt argument, --prompt, or --prompt-file',
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
if (promptText) {
|
||||
return options.promptArgument as string
|
||||
}
|
||||
|
||||
if (promptOptionText) {
|
||||
return options.promptOption as string
|
||||
}
|
||||
|
||||
if (!promptFilePath) {
|
||||
const error: CommandError = {
|
||||
code: 'MISSING_PROMPT',
|
||||
message: 'A prompt is required',
|
||||
details: 'Usage: paseo agent send [options] <id> [prompt] | --prompt <text> | --prompt-file <path>',
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
try {
|
||||
return await readFile(resolve(promptFilePath), 'utf8')
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
const error: CommandError = {
|
||||
code: 'PROMPT_FILE_READ_ERROR',
|
||||
message: `Failed to read prompt file: ${promptFilePath}`,
|
||||
details: message,
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function runSendCommand(
|
||||
agentIdArg: string,
|
||||
prompt: string,
|
||||
prompt: string | undefined,
|
||||
options: AgentSendOptions,
|
||||
_command: Command
|
||||
): Promise<SingleResult<AgentSendResult>> {
|
||||
@@ -90,19 +140,16 @@ export async function runSendCommand(
|
||||
const error: CommandError = {
|
||||
code: 'MISSING_AGENT_ID',
|
||||
message: 'Agent ID is required',
|
||||
details: 'Usage: paseo agent send [options] <id> <prompt>',
|
||||
details: 'Usage: paseo agent send [options] <id> [prompt]',
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
if (!prompt || prompt.trim().length === 0) {
|
||||
const error: CommandError = {
|
||||
code: 'MISSING_PROMPT',
|
||||
message: 'A prompt is required',
|
||||
details: 'Usage: paseo agent send [options] <id> <prompt>',
|
||||
}
|
||||
throw error
|
||||
}
|
||||
const promptInput = await resolvePromptInput({
|
||||
promptArgument: prompt,
|
||||
promptOption: options.prompt,
|
||||
promptFile: options.promptFile,
|
||||
})
|
||||
|
||||
let client
|
||||
try {
|
||||
@@ -124,7 +171,7 @@ export async function runSendCommand(
|
||||
: undefined
|
||||
|
||||
// Send the message
|
||||
await client.sendAgentMessage(agentIdArg, prompt, { images })
|
||||
await client.sendAgentMessage(agentIdArg, promptInput, { images })
|
||||
|
||||
// If --no-wait, return immediately
|
||||
if (options.noWait) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
import assert from 'node:assert'
|
||||
import { $ } from 'zx'
|
||||
import { mkdtemp, rm } from 'fs/promises'
|
||||
import { mkdtemp, rm, writeFile } from 'fs/promises'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
|
||||
@@ -30,6 +30,8 @@ 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-'))
|
||||
const promptFilePath = join(paseoHome, 'send-prompt.txt')
|
||||
await writeFile(promptFilePath, 'prompt from file')
|
||||
|
||||
try {
|
||||
// Test 1: send --help shows options
|
||||
@@ -37,14 +39,18 @@ try {
|
||||
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('--prompt'), 'help should mention --prompt option')
|
||||
assert(result.stdout.includes('--prompt-file'), 'help should mention --prompt-file option')
|
||||
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')
|
||||
assert(result.stdout.includes('<prompt>'), 'help should mention prompt argument')
|
||||
assert(result.stdout.includes('[prompt]'), 'help should mention optional prompt argument')
|
||||
console.log(' help should mention --prompt option')
|
||||
console.log(' help should mention --prompt-file option')
|
||||
console.log(' help should mention --no-wait flag')
|
||||
console.log(' help should mention --host option')
|
||||
console.log(' help should mention <id> argument')
|
||||
console.log(' help should mention <prompt> argument')
|
||||
console.log(' help should mention [prompt] argument')
|
||||
console.log('✓ send --help shows options\n')
|
||||
}
|
||||
|
||||
@@ -107,6 +113,28 @@ try {
|
||||
console.log('✓ send --no-wait flag is accepted\n')
|
||||
}
|
||||
|
||||
// Test 5b: send --prompt flag is accepted
|
||||
{
|
||||
console.log('Test 5b: send --prompt flag is accepted')
|
||||
const result =
|
||||
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo send --prompt "test prompt" abc123`.nothrow()
|
||||
const output = result.stdout + result.stderr
|
||||
assert(!output.includes('unknown option'), 'should accept --prompt flag')
|
||||
assert(!output.includes('error: option'), 'should not have option parsing error')
|
||||
console.log('✓ send --prompt flag is accepted\n')
|
||||
}
|
||||
|
||||
// Test 5c: send --prompt-file flag is accepted
|
||||
{
|
||||
console.log('Test 5c: send --prompt-file flag is accepted')
|
||||
const result =
|
||||
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo send --prompt-file ${promptFilePath} abc123`.nothrow()
|
||||
const output = result.stdout + result.stderr
|
||||
assert(!output.includes('unknown option'), 'should accept --prompt-file flag')
|
||||
assert(!output.includes('error: option'), 'should not have option parsing error')
|
||||
console.log('✓ send --prompt-file flag is accepted\n')
|
||||
}
|
||||
|
||||
// Test 6: send --host flag is accepted
|
||||
{
|
||||
console.log('Test 6: send --host flag is accepted')
|
||||
@@ -140,6 +168,20 @@ try {
|
||||
console.log('✓ Combined flags work together\n')
|
||||
}
|
||||
|
||||
// Test 8b: conflicting prompt sources are rejected
|
||||
{
|
||||
console.log('Test 8b: conflicting prompt sources are rejected')
|
||||
const result =
|
||||
await $`PASEO_HOST=localhost:${port} PASEO_HOME=${paseoHome} npx paseo send abc123 "positional prompt" --prompt "flag prompt"`.nothrow()
|
||||
assert.notStrictEqual(result.exitCode, 0, 'should fail for conflicting prompt sources')
|
||||
const output = result.stdout + result.stderr
|
||||
assert(
|
||||
output.includes('Provide exactly one of prompt argument, --prompt, or --prompt-file'),
|
||||
'should explain conflicting prompt sources'
|
||||
)
|
||||
console.log('✓ conflicting prompt sources are rejected\n')
|
||||
}
|
||||
|
||||
// Test 9: paseo --help shows send command
|
||||
{
|
||||
console.log('Test 9: paseo --help shows send command')
|
||||
|
||||
Reference in New Issue
Block a user