diff --git a/src/cli/commands/notify/send.ts b/src/cli/commands/notify/send.ts index bf8ca0d..f60363c 100644 --- a/src/cli/commands/notify/send.ts +++ b/src/cli/commands/notify/send.ts @@ -1,4 +1,3 @@ -import { Args } from '@oclif/core'; import { WorkEngine } from '../../../core/index.js'; import { BaseCommand } from '../../base-command.js'; import { formatOutput } from '../../formatter.js'; @@ -8,61 +7,93 @@ export default class NotifySend extends BaseCommand { 'Send work item notifications to configured targets'; static override examples = [ + '<%= config.bin %> notify <%= command.id %> TASK-001 to alerts', '<%= config.bin %> notify <%= command.id %> where state=new to alerts', '<%= config.bin %> notify <%= command.id %> where priority=high to team-notifications', '<%= config.bin %> notify <%= command.id %> where assignee=human-alice to human-alerts', ]; - static override args = { - subcommand: Args.string({ - description: 'where', - required: true, - }), - query: Args.string({ - description: 'query expression', - required: true, - }), - to: Args.string({ - description: 'to', - required: true, - }), - target: Args.string({ - description: 'target name', - required: true, - }), - }; + static override strict = false; static override flags = { ...BaseCommand.baseFlags, }; public async run(): Promise { - const { args } = await this.parse(NotifySend); + const { argv } = await this.parse(NotifySend); + + // Parse arguments from argv + const args = argv as string[]; - // Validate command structure - if (args.subcommand !== 'where') { - this.error('Expected "where" keyword after notify send'); + if (args.length < 3) { + this.error( + 'Invalid syntax. Use: work notify send TASK-001 to OR work notify send where to ' + ); } - if (args.to !== 'to') { - this.error('Expected "to" keyword before target name'); + let query: string; + let target: string; + + // Support two syntaxes: + // 1. Shorthand: work notify send TASK-001 to alerts + // args = ['TASK-001', 'to', 'alerts'] + // 2. Full: work notify send where id=TASK-001 to alerts + // args = ['where', 'id=TASK-001', 'to', 'alerts'] + if (args[0] === 'where') { + // Full syntax: where to + if (args.length < 4) { + this.error('Expected: work notify send where to '); + } + + const toIndex = args.indexOf('to'); + if (toIndex === -1) { + this.error('Expected "to" keyword before target name'); + } + + // Query is everything between 'where' and 'to' + query = args.slice(1, toIndex).join(' '); + // Target is everything after 'to' + target = args.slice(toIndex + 1).join(' '); + + if (!query || !target) { + this.error('Expected: work notify send where to '); + } + } else { + // Shorthand syntax: to + if (args[1] !== 'to') { + this.error( + 'Invalid syntax. Use: work notify send TASK-001 to OR work notify send where to ' + ); + } + + if (!args[0]) { + this.error('Task ID cannot be empty'); + } + + // Convert shorthand to query format + query = `id=${args[0]}`; + target = args.slice(2).join(' '); + + if (!target) { + this.error('Expected target name after "to"'); + } } const engine = new WorkEngine(); try { // Execute query to get work items - const workItems = await engine.listWorkItems(args.query); + const workItems = await engine.listWorkItems(query); // Send notification - const result = await engine.sendNotification(workItems, args.target); + const result = await engine.sendNotification(workItems, target); if (!result.success) { this.error(result.error || 'Notification failed'); } const output = formatOutput( - `Notification sent successfully to ${args.target} (${workItems.length} items)`, + `Notification sent successfully to ${target} (${workItems.length} items)`, (await this.getJsonMode()) ? 'json' : 'table' ); diff --git a/tests/e2e/notify-workflow.test.ts b/tests/e2e/notify-workflow.test.ts index e5ed54b..55e885c 100644 --- a/tests/e2e/notify-workflow.test.ts +++ b/tests/e2e/notify-workflow.test.ts @@ -92,6 +92,34 @@ describe('Notify Workflow E2E', () => { expect(logData.items).toHaveLength(0); }); + it('should support shorthand syntax for work item IDs', async () => { + const binPath = join(originalCwd, 'bin/run.js'); + + // Add notification target + execSync( + `node ${binPath} notify target add alerts --type bash --script work:log`, + { stdio: 'pipe' } + ); + + // Test shorthand syntax: work notify send TASK-001 to alerts + const sendOutput = execSync( + `node ${binPath} notify send TASK-001 to alerts`, + { encoding: 'utf8' } + ); + + expect(sendOutput).toContain('Notification sent successfully'); + expect(sendOutput).toContain('0 items'); // No work items with that ID, but syntax is valid + + // Verify notification log file was created + const notificationsDir = join(os.homedir(), '.work', 'notifications'); + const files = await fs.readdir(notificationsDir); + const logFiles = files.filter( + f => f.startsWith('notification-') && f.endsWith('.json') + ); + + expect(logFiles.length).toBeGreaterThan(0); + }); + it('should handle human-in-the-loop workflow', async () => { const binPath = join(originalCwd, 'bin/run.js');