Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 59 additions & 28 deletions src/cli/commands/notify/send.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<void> {
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 <target> OR work notify send where <query> to <target>'
);
}

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 <query> to <target>
if (args.length < 4) {
this.error('Expected: work notify send where <query> to <target>');
}

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 <query> to <target>');
}
} else {
// Shorthand syntax: <id> to <target>
if (args[1] !== 'to') {
this.error(
'Invalid syntax. Use: work notify send TASK-001 to <target> OR work notify send where <query> to <target>'
);
}

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'
);

Expand Down
28 changes: 28 additions & 0 deletions tests/e2e/notify-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down