@critical-path/client 0.20.5 → 0.20.6

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/src/bin/cli.ts DELETED
@@ -1,480 +0,0 @@
1
- #!/usr/bin/env node
2
- import { fileURLToPath } from 'node:url';
3
- import fs from 'node:fs';
4
- import { CriticalPathClient, type TaskTodoItem } from '../index.js';
5
-
6
- interface CliContext {
7
- apiUrl?: string;
8
- apiKey?: string;
9
- taskId?: string;
10
- projectId?: string;
11
- }
12
-
13
- function parseCliArgs(args: string[]) {
14
- const flags: Record<string, string | boolean | string[]> = {};
15
- const positionals: string[] = [];
16
-
17
- for (let i = 0; i < args.length; i++) {
18
- const arg = args[i];
19
- if (arg.startsWith('--')) {
20
- const key = arg.slice(2);
21
- const nextArg = args[i + 1];
22
- if (nextArg && !nextArg.startsWith('--')) {
23
- if (flags[key]) {
24
- if (Array.isArray(flags[key])) {
25
- (flags[key] as string[]).push(nextArg);
26
- } else {
27
- flags[key] = [flags[key] as string, nextArg];
28
- }
29
- } else {
30
- flags[key] = nextArg;
31
- }
32
- i++;
33
- } else {
34
- flags[key] = true;
35
- }
36
- } else if (arg.startsWith('-')) {
37
- const key = arg.slice(1);
38
- flags[key] = true;
39
- } else {
40
- positionals.push(arg);
41
- }
42
- }
43
-
44
- return { flags, positionals };
45
- }
46
-
47
- function getContext(flags: Record<string, any>): {
48
- client: CriticalPathClient;
49
- taskId?: string;
50
- projectId?: string;
51
- agentName?: string;
52
- } {
53
- const apiUrl = (flags.api as string) || process.env.CRITICAL_PATH_API;
54
- const apiKey = (flags.key as string) || process.env.CRITICAL_PATH_KEY;
55
- const taskId = (flags.task as string) || process.env.CRITICAL_PATH_TASK_ID;
56
- const projectId = (flags.project as string) || process.env.CRITICAL_PATH_PROJECT_ID;
57
-
58
- if (!apiUrl) {
59
- console.error('[critical-path] Error: Base API URL is required. Provide --api or set CRITICAL_PATH_API.');
60
- process.exit(1);
61
- }
62
-
63
- const headers: Record<string, string> = {};
64
- if (apiKey) {
65
- headers['Authorization'] = `Bearer ${apiKey}`;
66
- }
67
-
68
- const client = new CriticalPathClient({ baseUrl: apiUrl, headers });
69
- return { client, taskId, projectId };
70
- }
71
-
72
- function printHelp() {
73
- console.log(`
74
- Critical Path Agent CLI
75
-
76
- Usage:
77
- critical-path <command> [options]
78
-
79
- Commands:
80
- status <message> Update live execution activity / status
81
- block --reason <reason> [--pr <url>] Mark current task as blocked with explanation
82
- clarify --reason <reason> [--question <q>...] Post clarification request on task
83
- subtask <title> [--description <desc>] Create active child subtask under task
84
- subtask list List subtasks under current or specified task
85
- checklist add <title> Add a checklist item to current task
86
- checklist check <item> Mark a checklist item completed by title or ID
87
- checklist uncheck <item> Mark a checklist item incomplete
88
- checklist list List all checklist items on current task
89
- checklist set <item1> <item2>... Replace/initialize full checklist for task
90
- propose --title <title> [--description <desc>] Stage follow-up draft task on board
91
- deliverable --title <title> --url <url> Record output artifact or PR link
92
- comment <content> Post progress comment to task
93
-
94
- Options:
95
- --task <id> Target task ID (defaults to CRITICAL_PATH_TASK_ID)
96
- --project <id> Target project ID (defaults to CRITICAL_PATH_PROJECT_ID)
97
- --api <url> Critical Path API base URL (defaults to CRITICAL_PATH_API)
98
- --key <key> API Bearer token (defaults to CRITICAL_PATH_KEY)
99
- --help, -h Display this help message
100
- `);
101
- }
102
-
103
- export async function main(argv = process.argv.slice(2)): Promise<void> {
104
- const { flags, positionals } = parseCliArgs(argv);
105
- const command = positionals[0];
106
-
107
- if (!command || flags.help || command === 'help') {
108
- printHelp();
109
- return;
110
- }
111
-
112
- switch (command) {
113
- case 'status': {
114
- const statusMessage = positionals[1] || (flags.message as string);
115
- if (!statusMessage) {
116
- console.error('[critical-path] Error: Status message is required. Example: critical-path status "Running unit tests"');
117
- process.exit(1);
118
- }
119
- const { client, taskId, projectId } = getContext(flags);
120
- const isEngaged = !flags.disengaged;
121
- const details = flags.details as string | undefined;
122
-
123
- await client.updateStatus(statusMessage, {
124
- taskId,
125
- projectId,
126
- details,
127
- isEngaged
128
- });
129
- console.log(`[critical-path] Status updated: "${statusMessage}"`);
130
- break;
131
- }
132
-
133
- case 'block': {
134
- const reason = (flags.reason as string) || positionals[1];
135
- if (!reason) {
136
- console.error('[critical-path] Error: --reason is required when marking task blocked.');
137
- process.exit(1);
138
- }
139
- const { client, taskId } = getContext(flags);
140
- if (!taskId) {
141
- console.error('[critical-path] Error: Task ID is required (--task or CRITICAL_PATH_TASK_ID).');
142
- process.exit(1);
143
- }
144
- const prUrl = flags.pr as string | undefined;
145
-
146
- let commentContent = `Task marked as blocked: ${reason}`;
147
- if (prUrl) {
148
- commentContent += `\n\nPull Request: ${prUrl}`;
149
- }
150
-
151
- await client.addComment({
152
- taskId,
153
- content: commentContent,
154
- });
155
-
156
- await client.updateTask(taskId, {
157
- isBlocked: true,
158
- customFields: {
159
- blockerReason: reason,
160
- ...(prUrl ? { prUrl } : {})
161
- }
162
- });
163
-
164
- await client.updateStatus(`Blocked: ${reason}`, {
165
- taskId,
166
- isEngaged: false
167
- });
168
-
169
- console.log(`[critical-path] Task ${taskId} marked as blocked: "${reason}"`);
170
- break;
171
- }
172
-
173
- case 'clarify': {
174
- const reason = (flags.reason as string) || positionals[1];
175
- if (!reason) {
176
- console.error('[critical-path] Error: --reason is required when requesting clarification.');
177
- process.exit(1);
178
- }
179
- const { client, taskId } = getContext(flags);
180
- if (!taskId) {
181
- console.error('[critical-path] Error: Task ID is required (--task or CRITICAL_PATH_TASK_ID).');
182
- process.exit(1);
183
- }
184
-
185
- const questions = Array.isArray(flags.question)
186
- ? (flags.question as string[])
187
- : flags.question
188
- ? [flags.question as string]
189
- : [];
190
-
191
- let content = `### Clarification Needed\n\n**Reason:** ${reason}`;
192
- if (questions.length > 0) {
193
- content += `\n\n**Questions:**\n` + questions.map((q) => `- ${q}`).join('\n');
194
- }
195
-
196
- await client.addComment({
197
- taskId,
198
- content,
199
- });
200
-
201
- await client.updateTask(taskId, {
202
- isBlocked: true,
203
- customFields: {
204
- needsClarification: true,
205
- clarificationReason: reason
206
- }
207
- });
208
-
209
- await client.updateStatus('Awaiting clarification', {
210
- taskId,
211
- isEngaged: false
212
- });
213
-
214
- console.log(`[critical-path] Clarification request posted on task ${taskId}.`);
215
- break;
216
- }
217
-
218
- case 'propose': {
219
- const title = (flags.title as string) || positionals[1];
220
- if (!title) {
221
- console.error('[critical-path] Error: --title is required when proposing follow-up task.');
222
- process.exit(1);
223
- }
224
- const description = (flags.description as string) || '';
225
- const { client, taskId, projectId } = getContext(flags);
226
-
227
- if (!projectId) {
228
- console.error('[critical-path] Error: Project ID is required (--project or CRITICAL_PATH_PROJECT_ID).');
229
- process.exit(1);
230
- }
231
-
232
- const created = await client.createTask({
233
- projectId,
234
- title,
235
- description,
236
- status: 'draft' as any,
237
- priority: (flags.priority as any) || 'medium',
238
- parentId: (flags.parent as string) || undefined,
239
- customFields: {
240
- proposedByAgent: true
241
- }
242
- });
243
-
244
- console.log(`[critical-path] Created draft proposal "${title}" (ID: ${created.id})`);
245
- break;
246
- }
247
-
248
- case 'deliverable': {
249
- const title = (flags.title as string) || positionals[1];
250
- const url = (flags.url as string) || (flags.pr as string);
251
- if (!title || !url) {
252
- console.error('[critical-path] Error: Both --title and --url are required for deliverable.');
253
- process.exit(1);
254
- }
255
- const { client, taskId, projectId } = getContext(flags);
256
-
257
- if (projectId) {
258
- try {
259
- await client.createDeliverable({
260
- projectId,
261
- title,
262
- status: 'delivered',
263
- outputUrls: [url]
264
- });
265
- } catch {
266
- // If deliverables entity is not supported by backend store, fallback to task metadata
267
- }
268
- }
269
-
270
- if (taskId) {
271
- await client.addComment({
272
- taskId,
273
- content: `Deliverable recorded: [${title}](${url})`,
274
- });
275
- await client.updateTask(taskId, {
276
- customFields: {
277
- deliverableUrl: url,
278
- deliverableTitle: title
279
- }
280
- });
281
- }
282
-
283
- console.log(`[critical-path] Recorded deliverable "${title}" (${url})`);
284
- break;
285
- }
286
-
287
- case 'comment': {
288
- const content = positionals.slice(1).join(' ') || (flags.content as string) || (flags.message as string);
289
- if (!content) {
290
- console.error('[critical-path] Error: Comment content is required.');
291
- process.exit(1);
292
- }
293
- const { client, taskId } = getContext(flags);
294
- if (!taskId) {
295
- console.error('[critical-path] Error: Task ID is required (--task or CRITICAL_PATH_TASK_ID).');
296
- process.exit(1);
297
- }
298
-
299
- await client.addComment({
300
- taskId,
301
- content,
302
- });
303
- console.log(`[critical-path] Added comment to task ${taskId}.`);
304
- break;
305
- }
306
-
307
- case 'subtask': {
308
- const subAction = positionals[1];
309
- const { client, taskId, projectId } = getContext(flags);
310
- const targetParentId = (flags.parent as string) || taskId;
311
-
312
- if (subAction === 'list') {
313
- const targetProj = (flags.project as string) || projectId;
314
- if (!targetParentId) {
315
- console.error('[critical-path] Error: Parent task ID is required (--task, --parent, or CRITICAL_PATH_TASK_ID).');
316
- process.exit(1);
317
- }
318
- const tasks = await client.getTasks(targetProj);
319
- const subtasks = tasks.filter((t) => t.parentId === targetParentId);
320
- if (subtasks.length === 0) {
321
- console.log(`[critical-path] No subtasks found for parent task ${targetParentId}.`);
322
- } else {
323
- console.log(`[critical-path] Subtasks for parent task ${targetParentId} (${subtasks.length}):`);
324
- for (const st of subtasks) {
325
- console.log(` - [${st.id}] (${st.status}): ${st.title}`);
326
- }
327
- }
328
- break;
329
- }
330
-
331
- const title =
332
- subAction === 'create'
333
- ? positionals.slice(2).join(' ') || (flags.title as string)
334
- : positionals.slice(1).join(' ') || (flags.title as string);
335
-
336
- if (!title) {
337
- console.error('[critical-path] Error: Subtask title is required. Example: critical-path subtask "Implement unit tests"');
338
- process.exit(1);
339
- }
340
-
341
- if (!targetParentId) {
342
- console.error('[critical-path] Error: Parent task ID is required (--task, --parent, or CRITICAL_PATH_TASK_ID).');
343
- process.exit(1);
344
- }
345
-
346
- let targetProj = (flags.project as string) || projectId;
347
- if (!targetProj) {
348
- const parentTask = await client.getTask(targetParentId);
349
- targetProj = parentTask.projectId;
350
- }
351
-
352
- const created = await client.createTask({
353
- projectId: targetProj,
354
- title,
355
- description: (flags.description as string) || '',
356
- parentId: targetParentId,
357
- status: (flags.status as any) || 'todo',
358
- priority: (flags.priority as any) || 'medium',
359
- assigneeId: (flags.assignee as string) || undefined,
360
- customFields: {
361
- createdByAgent: true
362
- }
363
- });
364
-
365
- console.log(`[critical-path] Created subtask "${created.title}" (ID: ${created.id}) under parent task ${targetParentId}.`);
366
- break;
367
- }
368
-
369
- case 'checklist':
370
- case 'todo': {
371
- const subAction = positionals[1];
372
- const { client, taskId } = getContext(flags);
373
- if (!taskId) {
374
- console.error('[critical-path] Error: Task ID is required (--task or CRITICAL_PATH_TASK_ID).');
375
- process.exit(1);
376
- }
377
-
378
- if (!subAction || subAction === 'list') {
379
- const task = await client.getTask(taskId);
380
- if (!task.todos || task.todos.length === 0) {
381
- console.log(`[critical-path] Task ${taskId} has no checklist items.`);
382
- } else {
383
- console.log(`[critical-path] Checklist for task ${taskId} (${task.todos.length}):`);
384
- for (const item of task.todos) {
385
- console.log(` ${item.completed ? '[x]' : '[ ]'} ${item.title} (${item.id})`);
386
- }
387
- }
388
- break;
389
- }
390
-
391
- if (subAction === 'check' || subAction === 'done' || subAction === 'complete') {
392
- const query = positionals.slice(2).join(' ') || (flags.item as string) || (flags.title as string) || (flags.id as string);
393
- if (!query) {
394
- console.error('[critical-path] Error: Checklist item ID or title is required. Example: critical-path checklist check "Write unit tests"');
395
- process.exit(1);
396
- }
397
- const updated = await client.toggleTodo(taskId, query, true);
398
- const item = updated.todos?.find(
399
- (t) => t.id === query || t.title.toLowerCase().includes(query.toLowerCase())
400
- );
401
- console.log(`[critical-path] Marked checklist item "${item?.title || query}" as completed on task ${taskId}.`);
402
- break;
403
- }
404
-
405
- if (subAction === 'uncheck') {
406
- const query = positionals.slice(2).join(' ') || (flags.item as string) || (flags.title as string) || (flags.id as string);
407
- if (!query) {
408
- console.error('[critical-path] Error: Checklist item ID or title is required.');
409
- process.exit(1);
410
- }
411
- const updated = await client.toggleTodo(taskId, query, false);
412
- const item = updated.todos?.find(
413
- (t) => t.id === query || t.title.toLowerCase().includes(query.toLowerCase())
414
- );
415
- console.log(`[critical-path] Marked checklist item "${item?.title || query}" as incomplete on task ${taskId}.`);
416
- break;
417
- }
418
-
419
- if (subAction === 'set') {
420
- const rawItems = positionals.slice(2);
421
- const items = rawItems.length > 0 ? rawItems : (flags.items as string[]) || [];
422
- if (items.length === 0) {
423
- console.error('[critical-path] Error: Items are required when using checklist set.');
424
- process.exit(1);
425
- }
426
- const newTodos: TaskTodoItem[] = items.map((title, i) => ({
427
- id: `todo_${Date.now()}_${i}_${Math.random().toString(36).substring(2, 6)}`,
428
- title,
429
- completed: false,
430
- createdAt: new Date().toISOString()
431
- }));
432
- await client.updateTask(taskId, {
433
- todos: newTodos,
434
- });
435
- console.log(`[critical-path] Set ${newTodos.length} checklist item(s) on task ${taskId}.`);
436
- break;
437
- }
438
-
439
- // Default or 'add'
440
- const title =
441
- subAction === 'add'
442
- ? positionals.slice(2).join(' ') || (flags.title as string) || (flags.item as string)
443
- : positionals.slice(1).join(' ') || (flags.title as string) || (flags.item as string);
444
-
445
- if (!title) {
446
- console.error('[critical-path] Error: Item title is required. Example: critical-path checklist add "Write unit tests"');
447
- process.exit(1);
448
- }
449
-
450
- const item = await client.addTodo(taskId, title);
451
- console.log(`[critical-path] Added checklist item "${item.title}" (${item.id}) to task ${taskId}.`);
452
- break;
453
- }
454
-
455
- default:
456
- console.error(`[critical-path] Unknown command: "${command}". Run "critical-path --help" for usage.`);
457
- process.exit(1);
458
- }
459
- }
460
-
461
- // Only invoke main when executed directly as CLI script
462
- export function isDirectExecution(
463
- scriptUrl: string = import.meta.url,
464
- argv1: string | undefined = process.argv[1]
465
- ): boolean {
466
- if (!argv1) return false;
467
- try {
468
- const scriptPath = fileURLToPath(scriptUrl);
469
- return fs.realpathSync(scriptPath) === fs.realpathSync(argv1);
470
- } catch {
471
- return false;
472
- }
473
- }
474
-
475
- if (isDirectExecution()) {
476
- main().catch((err) => {
477
- console.error('[critical-path] Execution error:', err instanceof Error ? err.message : err);
478
- process.exit(1);
479
- });
480
- }