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
79 changes: 79 additions & 0 deletions src/cli/commands/promote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Command } from 'commander';
import { getEventById, getRecentEvents, createEvent } from '../../core/eventService.js';
import { findOrCreateTask } from '../../core/taskService.js';
import { findOrCreateTopic } from '../../core/topicService.js';
import { findOrCreateProject } from '../../core/projectService.js';
import { formatNextActionsOutput } from '../../lib/formatting.js';

export function registerPromote(program: Command): void {
program
.command('promote')
.description('Promote a next action to a task (list next actions if --next is omitted)')
.option('--next <event-id>', 'Next action event ID to promote')
.option('--title <text>', 'Task title (defaults to next action summary)')
.option('--topic <name>', 'Topic name')
.option('--project <name>', 'Project name')
.action(async (options) => {
try {
// No --next: list recent next actions
if (!options.next) {
const events = getRecentEvents({
eventTypes: ['next_action_defined'],
limit: 20,
});
console.log(formatNextActionsOutput(events));
return;
}

// Find the target event
const event = getEventById(options.next);
if (!event) {
console.error(`Event not found: ${options.next}`);
process.exit(1);
}
if (event.event_type !== 'next_action_defined') {
console.error(`Event ${options.next} is not a next_action_defined event (got: ${event.event_type})`);
process.exit(1);
}

// Resolve project / topic
let projectId: string | undefined;
if (options.project) {
const project = findOrCreateProject(options.project);
projectId = project.id;
}

let topicId: string | undefined;
if (options.topic) {
topicId = findOrCreateTopic(options.topic, projectId).id;
} else if (event.topic_id) {
// Inherit topic from the original next_action event
topicId = event.topic_id;
}

// Create the task
const taskTitle = options.title ?? event.summary;
const task = findOrCreateTask(taskTitle, projectId);

// Record task_started event linked to the new task
const startEvent = createEvent({
event_type: 'task_started',
task_id: task.id,
topic_id: topicId,
project_id: projectId,
actor: 'human',
origin: 'manual',
summary: `Promoted from next action: ${event.summary}`,
});

console.log(`Task created: ${task.id}`);
console.log(`Title : ${task.title}`);
console.log(`Event : ${startEvent.id}`);
console.log(`Source : next_action ${event.id} (${event.occurred_at})`);
if (topicId) console.log(`Topic : ${topicId}`);
} catch (err) {
console.error('Error:', err instanceof Error ? err.message : String(err));
process.exit(1);
}
});
}
2 changes: 2 additions & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { registerLog } from './commands/log.js';
import { registerTopics } from './commands/topics.js';
import { registerTasks } from './commands/tasks.js';
import { registerShow } from './commands/show.js';
import { registerPromote } from './commands/promote.js';

// Ensure ~/.worklog directory and DB exist on startup
const worklogDir = join(process.env.HOME ?? '.', '.worklog');
Expand Down Expand Up @@ -50,5 +51,6 @@ registerLog(program);
registerTopics(program);
registerTasks(program);
registerShow(program);
registerPromote(program);

program.parse();
2 changes: 1 addition & 1 deletion src/core/eventService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export function createEvent(input: CreateEventInput): Event {
return getEventById(id)!;
}

function getEventById(id: string): Event | null {
export function getEventById(id: string): Event | null {
const db = getDb();
const stmt = db.prepare('SELECT * FROM events WHERE id = ?');
return (stmt.get(id) as Event) ?? null;
Expand Down
22 changes: 22 additions & 0 deletions src/lib/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,28 @@ export function formatTopicsOutput(
return lines.join('\n');
}

export function formatNextActionsOutput(events: Event[]): string {
if (events.length === 0) {
return 'No next actions found.';
}

const lines: string[] = [];
lines.push('='.repeat(72));
lines.push(`NEXT ACTIONS (${events.length} entries)`);
lines.push('='.repeat(72));
lines.push(`${'ID'.padEnd(21)} ${'DATE'.padEnd(16)} ${'SUMMARY'}`);
lines.push('-'.repeat(72));

for (const e of events) {
const date = formatDate(e.occurred_at).padEnd(16);
lines.push(`${e.id.padEnd(21)} ${date} ${truncate(e.summary, 30)}`);
}

lines.push('='.repeat(72));
lines.push('To promote: ingest promote --next <ID> [--title <custom title>]');
return lines.join('\n');
}

const STATUS_LABELS: Record<string, string> = {
active: 'ACTIVE',
paused: 'PAUSED',
Expand Down
Loading