@lovelybunch/cli 1.0.72 → 1.0.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +27 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +19 -17
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +173 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/events.d.ts +3 -0
- package/dist/commands/events.d.ts.map +1 -0
- package/dist/commands/events.js +91 -0
- package/dist/commands/events.js.map +1 -0
- package/dist/commands/knowledge.d.ts +3 -0
- package/dist/commands/knowledge.d.ts.map +1 -0
- package/dist/commands/knowledge.js +270 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +14 -35
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/propose.d.ts.map +1 -1
- package/dist/commands/propose.js +18 -13
- package/dist/commands/propose.js.map +1 -1
- package/dist/commands/resource.d.ts +4 -0
- package/dist/commands/resource.d.ts.map +1 -0
- package/dist/commands/resource.js +570 -0
- package/dist/commands/resource.js.map +1 -0
- package/dist/commands/show.js +2 -2
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/task.d.ts +3 -0
- package/dist/commands/task.d.ts.map +1 -0
- package/dist/commands/task.js +643 -0
- package/dist/commands/task.js.map +1 -0
- package/package.json +6 -5
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { promises as fs } from 'fs';
|
|
6
|
+
import { table } from 'table';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { listProposals, getProposal, createProposal, updateProposal, deleteProposal, addPlanStep, addComment, } from '@lovelybunch/core';
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Constants & Helpers
|
|
11
|
+
// ============================================================================
|
|
12
|
+
const VALID_STATUSES = ['draft', 'proposed', 'in-review', 'code-complete', 'approved', 'merged', 'rejected'];
|
|
13
|
+
const VALID_PRIORITIES = ['low', 'medium', 'high', 'critical'];
|
|
14
|
+
function parseTags(tagsString) {
|
|
15
|
+
if (!tagsString)
|
|
16
|
+
return undefined;
|
|
17
|
+
const tags = tagsString
|
|
18
|
+
.split(',')
|
|
19
|
+
.map(tag => tag.trim())
|
|
20
|
+
.filter(tag => tag.length > 0);
|
|
21
|
+
return tags.length > 0 ? tags : undefined;
|
|
22
|
+
}
|
|
23
|
+
function validatePriority(priority) {
|
|
24
|
+
if (!priority)
|
|
25
|
+
return undefined;
|
|
26
|
+
const normalized = priority.toLowerCase();
|
|
27
|
+
if (!VALID_PRIORITIES.includes(normalized)) {
|
|
28
|
+
throw new Error(`Invalid priority "${priority}". Must be one of: ${VALID_PRIORITIES.join(', ')}`);
|
|
29
|
+
}
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
function validateStatus(status) {
|
|
33
|
+
if (!status)
|
|
34
|
+
return undefined;
|
|
35
|
+
const normalized = status.toLowerCase();
|
|
36
|
+
if (!VALID_STATUSES.includes(normalized)) {
|
|
37
|
+
throw new Error(`Invalid status "${status}". Must be one of: ${VALID_STATUSES.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
function interpretEscapeSequences(str) {
|
|
42
|
+
return str
|
|
43
|
+
.replace(/\\n/g, '\n')
|
|
44
|
+
.replace(/\\t/g, '\t')
|
|
45
|
+
.replace(/\\r/g, '\r')
|
|
46
|
+
.replace(/\\\\/g, '\\');
|
|
47
|
+
}
|
|
48
|
+
function getStatusColor(status) {
|
|
49
|
+
const colors = {
|
|
50
|
+
draft: chalk.gray,
|
|
51
|
+
proposed: chalk.blue,
|
|
52
|
+
'in-review': chalk.yellow,
|
|
53
|
+
'code-complete': chalk.green,
|
|
54
|
+
approved: chalk.green,
|
|
55
|
+
merged: chalk.cyan,
|
|
56
|
+
rejected: chalk.red,
|
|
57
|
+
};
|
|
58
|
+
return colors[status] || chalk.white;
|
|
59
|
+
}
|
|
60
|
+
function getPriorityColor(priority) {
|
|
61
|
+
const colors = {
|
|
62
|
+
low: chalk.gray,
|
|
63
|
+
medium: chalk.blue,
|
|
64
|
+
high: chalk.yellow,
|
|
65
|
+
critical: chalk.red,
|
|
66
|
+
};
|
|
67
|
+
return colors[priority] || chalk.white;
|
|
68
|
+
}
|
|
69
|
+
function getStepStatusIcon(status) {
|
|
70
|
+
const icons = {
|
|
71
|
+
pending: chalk.gray('○'),
|
|
72
|
+
'in-progress': chalk.yellow('◐'),
|
|
73
|
+
completed: chalk.green('●'),
|
|
74
|
+
failed: chalk.red('✗'),
|
|
75
|
+
};
|
|
76
|
+
return icons[status] || chalk.gray('○');
|
|
77
|
+
}
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Parent Command
|
|
80
|
+
// ============================================================================
|
|
81
|
+
export const taskCommand = new Command('task')
|
|
82
|
+
.description('Manage tasks (change proposals)')
|
|
83
|
+
.alias('proposal')
|
|
84
|
+
.addHelpText('after', `
|
|
85
|
+
Examples:
|
|
86
|
+
${chalk.gray('# List all tasks')}
|
|
87
|
+
nut task list
|
|
88
|
+
nut task list --status draft
|
|
89
|
+
nut task list --search "authentication"
|
|
90
|
+
|
|
91
|
+
${chalk.gray('# Get task details')}
|
|
92
|
+
nut task get cp-1234567890
|
|
93
|
+
|
|
94
|
+
${chalk.gray('# Create a new task')}
|
|
95
|
+
nut task create "Add user authentication"
|
|
96
|
+
nut task create "Fix bug" --priority high --tags "bug,urgent"
|
|
97
|
+
|
|
98
|
+
${chalk.gray('# Update a task')}
|
|
99
|
+
nut task update cp-1234567890 --status in-review
|
|
100
|
+
nut task update cp-1234567890 --priority critical
|
|
101
|
+
|
|
102
|
+
${chalk.gray('# Add plan steps')}
|
|
103
|
+
nut task step cp-1234567890 "Implement feature"
|
|
104
|
+
nut task step cp-1234567890 "Run tests" --command "npm test"
|
|
105
|
+
|
|
106
|
+
${chalk.gray('# Add comments')}
|
|
107
|
+
nut task comment cp-1234567890 "Looks good, approved!"
|
|
108
|
+
nut task comment cp-1234567890 "Please review" --author "reviewer@example.com"
|
|
109
|
+
|
|
110
|
+
${chalk.gray('# Delete a task')}
|
|
111
|
+
nut task delete cp-1234567890
|
|
112
|
+
`);
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// task list
|
|
115
|
+
// ============================================================================
|
|
116
|
+
taskCommand
|
|
117
|
+
.command('list')
|
|
118
|
+
.description('List all tasks')
|
|
119
|
+
.option('-s, --status <status>', 'Filter by status (draft/proposed/in-review/code-complete/approved/merged/rejected)')
|
|
120
|
+
.option('-a, --author <author>', 'Filter by author name or email')
|
|
121
|
+
.option('-p, --priority <priority>', 'Filter by priority (low/medium/high/critical)')
|
|
122
|
+
.option('-q, --search <query>', 'Search tasks by content (fuzzy search)')
|
|
123
|
+
.option('-t, --tags <tags>', 'Filter by tags (comma-separated)')
|
|
124
|
+
.option('-l, --limit <number>', 'Limit number of results', '20')
|
|
125
|
+
.option('--json', 'Output as JSON')
|
|
126
|
+
.action(async (options) => {
|
|
127
|
+
const spinner = ora('Loading tasks...').start();
|
|
128
|
+
try {
|
|
129
|
+
const filters = {};
|
|
130
|
+
if (options.status) {
|
|
131
|
+
filters.status = validateStatus(options.status);
|
|
132
|
+
}
|
|
133
|
+
if (options.author) {
|
|
134
|
+
filters.author = options.author;
|
|
135
|
+
}
|
|
136
|
+
if (options.priority) {
|
|
137
|
+
filters.priority = validatePriority(options.priority);
|
|
138
|
+
}
|
|
139
|
+
if (options.tags) {
|
|
140
|
+
filters.tags = parseTags(options.tags);
|
|
141
|
+
}
|
|
142
|
+
if (options.search) {
|
|
143
|
+
filters.search = options.search;
|
|
144
|
+
}
|
|
145
|
+
const proposals = await listProposals(filters);
|
|
146
|
+
const limit = parseInt(options.limit, 10) || 20;
|
|
147
|
+
const limited = proposals.slice(0, limit);
|
|
148
|
+
spinner.stop();
|
|
149
|
+
if (options.json) {
|
|
150
|
+
console.log(JSON.stringify(limited, null, 2));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (limited.length === 0) {
|
|
154
|
+
console.log(chalk.yellow('No tasks found.'));
|
|
155
|
+
if (Object.keys(filters).length > 0) {
|
|
156
|
+
console.log(chalk.gray('Try adjusting your filters or search query.'));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.log(chalk.gray('Create one with: nut task create "Your intent"'));
|
|
160
|
+
}
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// Prepare table data
|
|
164
|
+
const tableData = [
|
|
165
|
+
[
|
|
166
|
+
chalk.bold('ID'),
|
|
167
|
+
chalk.bold('Intent'),
|
|
168
|
+
chalk.bold('Author'),
|
|
169
|
+
chalk.bold('Status'),
|
|
170
|
+
chalk.bold('Priority'),
|
|
171
|
+
chalk.bold('Created'),
|
|
172
|
+
],
|
|
173
|
+
];
|
|
174
|
+
for (const proposal of limited) {
|
|
175
|
+
const intent = proposal.intent.length > 35
|
|
176
|
+
? proposal.intent.substring(0, 32) + '...'
|
|
177
|
+
: proposal.intent;
|
|
178
|
+
const priority = proposal.metadata?.priority || 'medium';
|
|
179
|
+
tableData.push([
|
|
180
|
+
chalk.cyan(proposal.id),
|
|
181
|
+
intent,
|
|
182
|
+
proposal.author.name,
|
|
183
|
+
getStatusColor(proposal.status)(proposal.status),
|
|
184
|
+
getPriorityColor(priority)(priority),
|
|
185
|
+
chalk.gray(new Date(proposal.metadata?.createdAt || '').toLocaleDateString()),
|
|
186
|
+
]);
|
|
187
|
+
}
|
|
188
|
+
// Display table
|
|
189
|
+
const tableConfig = {
|
|
190
|
+
border: {
|
|
191
|
+
topBody: chalk.gray('─'),
|
|
192
|
+
topJoin: chalk.gray('┬'),
|
|
193
|
+
topLeft: chalk.gray('┌'),
|
|
194
|
+
topRight: chalk.gray('┐'),
|
|
195
|
+
bottomBody: chalk.gray('─'),
|
|
196
|
+
bottomJoin: chalk.gray('┴'),
|
|
197
|
+
bottomLeft: chalk.gray('└'),
|
|
198
|
+
bottomRight: chalk.gray('┘'),
|
|
199
|
+
bodyLeft: chalk.gray('│'),
|
|
200
|
+
bodyRight: chalk.gray('│'),
|
|
201
|
+
bodyJoin: chalk.gray('│'),
|
|
202
|
+
joinBody: chalk.gray('─'),
|
|
203
|
+
joinLeft: chalk.gray('├'),
|
|
204
|
+
joinRight: chalk.gray('┤'),
|
|
205
|
+
joinJoin: chalk.gray('┼'),
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
console.log('\n' + chalk.bold.underline('Tasks'));
|
|
209
|
+
console.log(table(tableData, tableConfig));
|
|
210
|
+
console.log(chalk.gray(`Showing ${limited.length} of ${proposals.length} tasks`));
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
spinner.fail('Failed to list tasks');
|
|
214
|
+
console.error(chalk.red('Error:'), error.message);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// task get
|
|
220
|
+
// ============================================================================
|
|
221
|
+
taskCommand
|
|
222
|
+
.command('get')
|
|
223
|
+
.description('Get details of a task')
|
|
224
|
+
.argument('<id>', 'Task ID (e.g., cp-1234567890)')
|
|
225
|
+
.option('--json', 'Output as JSON')
|
|
226
|
+
.option('--raw', 'Show raw content without formatting')
|
|
227
|
+
.action(async (id, options) => {
|
|
228
|
+
const spinner = ora('Loading task...').start();
|
|
229
|
+
try {
|
|
230
|
+
const proposal = await getProposal(id);
|
|
231
|
+
spinner.stop();
|
|
232
|
+
if (!proposal) {
|
|
233
|
+
console.error(chalk.red(`Task '${id}' not found.`));
|
|
234
|
+
console.log(chalk.gray('Use "nut task list" to see all tasks.'));
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
if (options.json) {
|
|
238
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (options.raw && proposal.content) {
|
|
242
|
+
console.log(proposal.content);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// Display formatted output
|
|
246
|
+
console.log('\n' + chalk.bold.underline('Task Details'));
|
|
247
|
+
console.log();
|
|
248
|
+
// Basic Information
|
|
249
|
+
console.log(chalk.cyan('Basic Information:'));
|
|
250
|
+
console.log(' ID: ' + chalk.white(proposal.id));
|
|
251
|
+
console.log(' Intent: ' + chalk.white(proposal.intent));
|
|
252
|
+
console.log(' Status: ' + getStatusColor(proposal.status)(proposal.status.toUpperCase()));
|
|
253
|
+
if (proposal.metadata?.priority) {
|
|
254
|
+
console.log(' Priority: ' + getPriorityColor(proposal.metadata.priority)(proposal.metadata.priority.toUpperCase()));
|
|
255
|
+
}
|
|
256
|
+
console.log(' Created: ' + chalk.white(new Date(proposal.metadata?.createdAt || '').toLocaleString()));
|
|
257
|
+
console.log(' Updated: ' + chalk.white(new Date(proposal.metadata?.updatedAt || '').toLocaleString()));
|
|
258
|
+
// Author Information
|
|
259
|
+
console.log('\n' + chalk.cyan('Author:'));
|
|
260
|
+
console.log(' Name: ' + chalk.white(proposal.author.name));
|
|
261
|
+
console.log(' Type: ' + chalk.white(proposal.author.type));
|
|
262
|
+
if (proposal.author.email) {
|
|
263
|
+
console.log(' Email: ' + chalk.white(proposal.author.email));
|
|
264
|
+
}
|
|
265
|
+
// Product Spec Reference
|
|
266
|
+
if (proposal.productSpecRef) {
|
|
267
|
+
console.log('\n' + chalk.cyan('Product Specification:'));
|
|
268
|
+
console.log(' Reference: ' + chalk.white(proposal.productSpecRef));
|
|
269
|
+
}
|
|
270
|
+
// Content
|
|
271
|
+
if (proposal.content) {
|
|
272
|
+
console.log('\n' + chalk.cyan('Content:'));
|
|
273
|
+
const contentLines = proposal.content.split('\n').slice(0, 10);
|
|
274
|
+
contentLines.forEach(line => console.log(' ' + chalk.gray(line)));
|
|
275
|
+
if (proposal.content.split('\n').length > 10) {
|
|
276
|
+
console.log(' ' + chalk.gray('... (truncated)'));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Plan Steps
|
|
280
|
+
if (proposal.planSteps && proposal.planSteps.length > 0) {
|
|
281
|
+
console.log('\n' + chalk.cyan('Plan Steps:'));
|
|
282
|
+
proposal.planSteps.forEach((step, index) => {
|
|
283
|
+
const statusIcon = getStepStatusIcon(step.status);
|
|
284
|
+
console.log(` ${index + 1}. ${statusIcon} ${step.description}`);
|
|
285
|
+
if (step.command) {
|
|
286
|
+
console.log(' Command: ' + chalk.gray(step.command));
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
// Tags
|
|
291
|
+
if (proposal.metadata?.tags && proposal.metadata.tags.length > 0) {
|
|
292
|
+
console.log('\n' + chalk.cyan('Tags:'));
|
|
293
|
+
console.log(' ' + proposal.metadata.tags.map(tag => chalk.magenta(`#${tag}`)).join(' '));
|
|
294
|
+
}
|
|
295
|
+
// Actions
|
|
296
|
+
console.log('\n' + chalk.cyan('Actions:'));
|
|
297
|
+
console.log(' • Update task: ' + chalk.yellow(`nut task update ${proposal.id} --status <status>`));
|
|
298
|
+
console.log(' • Delete task: ' + chalk.yellow(`nut task delete ${proposal.id}`));
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
spinner.fail('Failed to load task');
|
|
302
|
+
console.error(chalk.red('Error:'), error.message);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
// ============================================================================
|
|
307
|
+
// task create
|
|
308
|
+
// ============================================================================
|
|
309
|
+
taskCommand
|
|
310
|
+
.command('create')
|
|
311
|
+
.description('Create a new task')
|
|
312
|
+
.argument('<intent>', 'The intent/title of the task')
|
|
313
|
+
.option('--author <name>', 'Author name')
|
|
314
|
+
.option('--email <email>', 'Author email')
|
|
315
|
+
.option('--type <type>', 'Author type (human/agent)', 'human')
|
|
316
|
+
.option('-c, --content <text>', 'Detailed description or content')
|
|
317
|
+
.option('-f, --file <path>', 'Read content from a file')
|
|
318
|
+
.option('-t, --tags <tags>', 'Comma-separated tags')
|
|
319
|
+
.option('-p, --priority <level>', 'Priority: low, medium, high, critical')
|
|
320
|
+
.option('-s, --status <status>', 'Initial status (default: draft)')
|
|
321
|
+
.option('--spec <ref>', 'Product specification reference')
|
|
322
|
+
.option('--json', 'Output as JSON')
|
|
323
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
324
|
+
.action(async (intent, options) => {
|
|
325
|
+
try {
|
|
326
|
+
// Get author information
|
|
327
|
+
let authorName = options.author;
|
|
328
|
+
let authorEmail = options.email;
|
|
329
|
+
if (!options.yes && (!authorName || !authorEmail)) {
|
|
330
|
+
const authorInfo = await inquirer.prompt([
|
|
331
|
+
{
|
|
332
|
+
type: 'input',
|
|
333
|
+
name: 'name',
|
|
334
|
+
message: 'Author name:',
|
|
335
|
+
default: options.author || os.userInfo().username,
|
|
336
|
+
when: !options.author,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
type: 'input',
|
|
340
|
+
name: 'email',
|
|
341
|
+
message: 'Author email (optional):',
|
|
342
|
+
default: options.email,
|
|
343
|
+
when: !options.email,
|
|
344
|
+
},
|
|
345
|
+
]);
|
|
346
|
+
authorName = authorName || authorInfo.name;
|
|
347
|
+
authorEmail = authorEmail || authorInfo.email;
|
|
348
|
+
}
|
|
349
|
+
authorName = authorName || os.userInfo().username;
|
|
350
|
+
// Process content: --file takes precedence over --content
|
|
351
|
+
let content;
|
|
352
|
+
if (options.file) {
|
|
353
|
+
const spinner = ora(`Reading content from ${options.file}...`).start();
|
|
354
|
+
try {
|
|
355
|
+
content = await fs.readFile(options.file, 'utf-8');
|
|
356
|
+
spinner.stop();
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
spinner.fail(`Failed to read file: ${options.file}`);
|
|
360
|
+
console.error(chalk.red('Error:'), error.message);
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else if (options.content) {
|
|
365
|
+
content = interpretEscapeSequences(options.content);
|
|
366
|
+
}
|
|
367
|
+
const spinner = ora('Creating task...').start();
|
|
368
|
+
const proposal = await createProposal({
|
|
369
|
+
intent,
|
|
370
|
+
content: content || intent, // Use intent as content if no content provided
|
|
371
|
+
author: {
|
|
372
|
+
id: authorEmail || `${authorName}@local`,
|
|
373
|
+
name: authorName,
|
|
374
|
+
email: authorEmail,
|
|
375
|
+
type: options.type === 'agent' ? 'agent' : 'human',
|
|
376
|
+
},
|
|
377
|
+
status: validateStatus(options.status) || 'draft',
|
|
378
|
+
metadata: {
|
|
379
|
+
tags: parseTags(options.tags),
|
|
380
|
+
priority: validatePriority(options.priority),
|
|
381
|
+
},
|
|
382
|
+
productSpecRef: options.spec,
|
|
383
|
+
});
|
|
384
|
+
spinner.succeed(`Task created: ${chalk.cyan(proposal.id)}`);
|
|
385
|
+
if (options.json) {
|
|
386
|
+
console.log(JSON.stringify(proposal, null, 2));
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
console.log('\n' + chalk.bold('Task Details:'));
|
|
390
|
+
console.log(' ID: ' + chalk.cyan(proposal.id));
|
|
391
|
+
console.log(' Intent: ' + chalk.white(proposal.intent));
|
|
392
|
+
console.log(' Author: ' + chalk.white(`${proposal.author.name} (${proposal.author.type})`));
|
|
393
|
+
console.log(' Status: ' + getStatusColor(proposal.status)(proposal.status));
|
|
394
|
+
if (proposal.metadata?.priority) {
|
|
395
|
+
console.log(' Priority: ' + getPriorityColor(proposal.metadata.priority)(proposal.metadata.priority));
|
|
396
|
+
}
|
|
397
|
+
if (proposal.metadata?.tags && proposal.metadata.tags.length > 0) {
|
|
398
|
+
console.log(' Tags: ' + chalk.gray(proposal.metadata.tags.join(', ')));
|
|
399
|
+
}
|
|
400
|
+
console.log('\n' + chalk.cyan('Next steps:'));
|
|
401
|
+
console.log(' • View task: ' + chalk.yellow(`nut task get ${proposal.id}`));
|
|
402
|
+
console.log(' • Update task: ' + chalk.yellow(`nut task update ${proposal.id} --status proposed`));
|
|
403
|
+
console.log(' • List tasks: ' + chalk.yellow('nut task list'));
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
console.error(chalk.red('Failed to create task:'), error.message);
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
// ============================================================================
|
|
411
|
+
// task update
|
|
412
|
+
// ============================================================================
|
|
413
|
+
taskCommand
|
|
414
|
+
.command('update')
|
|
415
|
+
.description('Update an existing task')
|
|
416
|
+
.argument('<id>', 'Task ID to update')
|
|
417
|
+
.option('-i, --intent <text>', 'Update intent/title')
|
|
418
|
+
.option('-c, --content <text>', 'Update content')
|
|
419
|
+
.option('-f, --file <path>', 'Read new content from a file')
|
|
420
|
+
.option('-s, --status <status>', 'Update status')
|
|
421
|
+
.option('-p, --priority <level>', 'Update priority')
|
|
422
|
+
.option('-t, --tags <tags>', 'Update tags (comma-separated)')
|
|
423
|
+
.option('--spec <ref>', 'Update product specification reference')
|
|
424
|
+
.option('--json', 'Output as JSON')
|
|
425
|
+
.action(async (id, options) => {
|
|
426
|
+
try {
|
|
427
|
+
// Check if any update option is provided
|
|
428
|
+
const hasUpdates = options.intent || options.content || options.file ||
|
|
429
|
+
options.status || options.priority || options.tags || options.spec;
|
|
430
|
+
if (!hasUpdates) {
|
|
431
|
+
console.error(chalk.red('No updates specified.'));
|
|
432
|
+
console.log(chalk.gray('Use --intent, --content, --status, --priority, --tags, or --spec to update.'));
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
// Read content from file if specified
|
|
436
|
+
let content;
|
|
437
|
+
if (options.file) {
|
|
438
|
+
const spinner = ora(`Reading content from ${options.file}...`).start();
|
|
439
|
+
try {
|
|
440
|
+
content = await fs.readFile(options.file, 'utf-8');
|
|
441
|
+
spinner.stop();
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
spinner.fail(`Failed to read file: ${options.file}`);
|
|
445
|
+
console.error(chalk.red('Error:'), error.message);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
else if (options.content) {
|
|
450
|
+
content = interpretEscapeSequences(options.content);
|
|
451
|
+
}
|
|
452
|
+
const spinner = ora('Updating task...').start();
|
|
453
|
+
const updates = {};
|
|
454
|
+
if (options.intent)
|
|
455
|
+
updates.intent = options.intent;
|
|
456
|
+
if (content)
|
|
457
|
+
updates.content = content;
|
|
458
|
+
if (options.status)
|
|
459
|
+
updates.status = validateStatus(options.status);
|
|
460
|
+
if (options.spec !== undefined)
|
|
461
|
+
updates.productSpecRef = options.spec;
|
|
462
|
+
// Handle metadata updates
|
|
463
|
+
if (options.priority || options.tags) {
|
|
464
|
+
updates.metadata = {};
|
|
465
|
+
if (options.priority)
|
|
466
|
+
updates.metadata.priority = validatePriority(options.priority);
|
|
467
|
+
if (options.tags)
|
|
468
|
+
updates.metadata.tags = parseTags(options.tags);
|
|
469
|
+
}
|
|
470
|
+
const updated = await updateProposal(id, updates);
|
|
471
|
+
spinner.succeed(`Task updated: ${chalk.cyan(id)}`);
|
|
472
|
+
if (options.json) {
|
|
473
|
+
console.log(JSON.stringify(updated, null, 2));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
console.log('\n' + chalk.bold('Updated Task:'));
|
|
477
|
+
console.log(' ID: ' + chalk.cyan(updated.id));
|
|
478
|
+
console.log(' Intent: ' + chalk.white(updated.intent));
|
|
479
|
+
console.log(' Status: ' + getStatusColor(updated.status)(updated.status));
|
|
480
|
+
if (updated.metadata?.priority) {
|
|
481
|
+
console.log(' Priority: ' + getPriorityColor(updated.metadata.priority)(updated.metadata.priority));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
console.error(chalk.red('Failed to update task:'), error.message);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
// ============================================================================
|
|
490
|
+
// task delete
|
|
491
|
+
// ============================================================================
|
|
492
|
+
taskCommand
|
|
493
|
+
.command('delete')
|
|
494
|
+
.description('Delete a task')
|
|
495
|
+
.argument('<id>', 'Task ID to delete')
|
|
496
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
497
|
+
.action(async (id, options) => {
|
|
498
|
+
try {
|
|
499
|
+
// Verify task exists first
|
|
500
|
+
const spinner = ora('Loading task...').start();
|
|
501
|
+
const proposal = await getProposal(id);
|
|
502
|
+
spinner.stop();
|
|
503
|
+
if (!proposal) {
|
|
504
|
+
console.error(chalk.red(`Task '${id}' not found.`));
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
// Confirm deletion
|
|
508
|
+
if (!options.yes) {
|
|
509
|
+
console.log('\n' + chalk.bold('Task to delete:'));
|
|
510
|
+
console.log(' ID: ' + chalk.cyan(proposal.id));
|
|
511
|
+
console.log(' Intent: ' + chalk.white(proposal.intent));
|
|
512
|
+
console.log(' Status: ' + getStatusColor(proposal.status)(proposal.status));
|
|
513
|
+
console.log();
|
|
514
|
+
const { confirm } = await inquirer.prompt([
|
|
515
|
+
{
|
|
516
|
+
type: 'confirm',
|
|
517
|
+
name: 'confirm',
|
|
518
|
+
message: chalk.yellow('Are you sure you want to delete this task?'),
|
|
519
|
+
default: false,
|
|
520
|
+
},
|
|
521
|
+
]);
|
|
522
|
+
if (!confirm) {
|
|
523
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const deleteSpinner = ora('Deleting task...').start();
|
|
528
|
+
const deleted = await deleteProposal(id);
|
|
529
|
+
if (deleted) {
|
|
530
|
+
deleteSpinner.succeed(`Task deleted: ${chalk.cyan(id)}`);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
deleteSpinner.fail('Failed to delete task');
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
console.error(chalk.red('Failed to delete task:'), error.message);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
// ============================================================================
|
|
543
|
+
// task step (add plan step)
|
|
544
|
+
// ============================================================================
|
|
545
|
+
taskCommand
|
|
546
|
+
.command('step')
|
|
547
|
+
.description('Add a plan step to a task')
|
|
548
|
+
.argument('<id>', 'Task ID to add step to')
|
|
549
|
+
.argument('<description>', 'Description of the step')
|
|
550
|
+
.option('-s, --status <status>', 'Initial status (pending/in-progress/completed/failed)', 'pending')
|
|
551
|
+
.option('-c, --command <cmd>', 'Command to execute for this step')
|
|
552
|
+
.option('-e, --expected <outcome>', 'Expected outcome of this step')
|
|
553
|
+
.option('--json', 'Output as JSON')
|
|
554
|
+
.action(async (id, description, options) => {
|
|
555
|
+
try {
|
|
556
|
+
const spinner = ora('Adding plan step...').start();
|
|
557
|
+
const validStepStatuses = ['pending', 'in-progress', 'completed', 'failed'];
|
|
558
|
+
const status = options.status?.toLowerCase();
|
|
559
|
+
if (status && !validStepStatuses.includes(status)) {
|
|
560
|
+
spinner.fail(`Invalid step status: ${options.status}`);
|
|
561
|
+
console.log(chalk.gray(`Valid statuses: ${validStepStatuses.join(', ')}`));
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
const step = await addPlanStep(id, {
|
|
565
|
+
description,
|
|
566
|
+
status: status,
|
|
567
|
+
command: options.command,
|
|
568
|
+
expectedOutcome: options.expected,
|
|
569
|
+
});
|
|
570
|
+
spinner.succeed(`Plan step added: ${chalk.cyan(step.id)}`);
|
|
571
|
+
if (options.json) {
|
|
572
|
+
console.log(JSON.stringify(step, null, 2));
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
console.log('\n' + chalk.bold('Step Details:'));
|
|
576
|
+
console.log(' ID: ' + chalk.cyan(step.id));
|
|
577
|
+
console.log(' Description: ' + chalk.white(step.description));
|
|
578
|
+
console.log(' Status: ' + getStepStatusIcon(step.status) + ' ' + step.status);
|
|
579
|
+
if (step.command) {
|
|
580
|
+
console.log(' Command: ' + chalk.gray(step.command));
|
|
581
|
+
}
|
|
582
|
+
if (step.expectedOutcome) {
|
|
583
|
+
console.log(' Expected: ' + chalk.gray(step.expectedOutcome));
|
|
584
|
+
}
|
|
585
|
+
console.log('\n' + chalk.cyan('Next steps:'));
|
|
586
|
+
console.log(' • View task: ' + chalk.yellow(`nut task get ${id}`));
|
|
587
|
+
console.log(' • Add more: ' + chalk.yellow(`nut task step ${id} "Next step"`));
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
console.error(chalk.red('Failed to add plan step:'), error.message);
|
|
591
|
+
process.exit(1);
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
// ============================================================================
|
|
595
|
+
// task comment (add comment)
|
|
596
|
+
// ============================================================================
|
|
597
|
+
taskCommand
|
|
598
|
+
.command('comment')
|
|
599
|
+
.description('Add a comment to a task')
|
|
600
|
+
.argument('<id>', 'Task ID to add comment to')
|
|
601
|
+
.argument('<content>', 'Comment content')
|
|
602
|
+
.option('-a, --author <author>', 'Comment author (email or name)')
|
|
603
|
+
.option('--json', 'Output as JSON')
|
|
604
|
+
.action(async (id, content, options) => {
|
|
605
|
+
try {
|
|
606
|
+
// Get author - use provided or prompt
|
|
607
|
+
let author = options.author;
|
|
608
|
+
if (!author) {
|
|
609
|
+
const inquirerPrompt = await inquirer.prompt([
|
|
610
|
+
{
|
|
611
|
+
type: 'input',
|
|
612
|
+
name: 'author',
|
|
613
|
+
message: 'Comment author (email or name):',
|
|
614
|
+
default: os.userInfo().username + '@local',
|
|
615
|
+
},
|
|
616
|
+
]);
|
|
617
|
+
author = inquirerPrompt.author;
|
|
618
|
+
}
|
|
619
|
+
const spinner = ora('Adding comment...').start();
|
|
620
|
+
const comment = await addComment(id, {
|
|
621
|
+
author,
|
|
622
|
+
content,
|
|
623
|
+
});
|
|
624
|
+
spinner.succeed(`Comment added: ${chalk.cyan(comment.id)}`);
|
|
625
|
+
if (options.json) {
|
|
626
|
+
console.log(JSON.stringify(comment, null, 2));
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
console.log('\n' + chalk.bold('Comment Details:'));
|
|
630
|
+
console.log(' ID: ' + chalk.cyan(comment.id));
|
|
631
|
+
console.log(' Author: ' + chalk.white(comment.author));
|
|
632
|
+
console.log(' Content: ' + chalk.gray(comment.content.substring(0, 80) + (comment.content.length > 80 ? '...' : '')));
|
|
633
|
+
console.log(' Created: ' + chalk.gray(new Date(comment.createdAt).toLocaleString()));
|
|
634
|
+
console.log('\n' + chalk.cyan('Next steps:'));
|
|
635
|
+
console.log(' • View task: ' + chalk.yellow(`nut task get ${id}`));
|
|
636
|
+
console.log(' • Add more: ' + chalk.yellow(`nut task comment ${id} "Another comment"`));
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
console.error(chalk.red('Failed to add comment:'), error.message);
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
//# sourceMappingURL=task.js.map
|