exponential-cli 1.0.0 → 1.1.0
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/README.md +17 -0
- package/dist/index.js +1836 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command11 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/auth.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -244,6 +244,30 @@ function transformWorkspace(workspace) {
|
|
|
244
244
|
type: workspace.type
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
|
+
function outputActionJson(action) {
|
|
248
|
+
const output = transformAction(action);
|
|
249
|
+
console.log(JSON.stringify(output, null, 2));
|
|
250
|
+
}
|
|
251
|
+
function outputActionPretty(action) {
|
|
252
|
+
const statusColor = getKanbanStatusColor(action.kanbanStatus);
|
|
253
|
+
const statusBadge = action.kanbanStatus ? chalk3[statusColor](`[${action.kanbanStatus}]`) : chalk3.gray("[NO STATUS]");
|
|
254
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
255
|
+
console.log(`
|
|
256
|
+
${statusBadge} ${chalk3.bold(action.name)}`);
|
|
257
|
+
console.log(chalk3.gray(` ID: ${action.id}`));
|
|
258
|
+
if (action.project) {
|
|
259
|
+
console.log(` ${chalk3.cyan("Project:")} ${action.project.name}`);
|
|
260
|
+
}
|
|
261
|
+
console.log(` ${chalk3.magenta("Priority:")} ${action.priority}`);
|
|
262
|
+
if (action.dueDate) {
|
|
263
|
+
const dueDate = new Date(action.dueDate);
|
|
264
|
+
console.log(` ${chalk3.yellow("Due:")} ${formatDate(dueDate)}`);
|
|
265
|
+
}
|
|
266
|
+
if (action.description) {
|
|
267
|
+
console.log(` ${chalk3.gray("Description:")} ${action.description.substring(0, 100)}${action.description.length > 100 ? "..." : ""}`);
|
|
268
|
+
}
|
|
269
|
+
console.log();
|
|
270
|
+
}
|
|
247
271
|
function outputActionsJson(actions, filters = {}) {
|
|
248
272
|
const output = {
|
|
249
273
|
actions: actions.map(transformAction),
|
|
@@ -365,8 +389,650 @@ function formatDate(date) {
|
|
|
365
389
|
day: "numeric"
|
|
366
390
|
});
|
|
367
391
|
}
|
|
392
|
+
function outputContactJson(contact) {
|
|
393
|
+
console.log(JSON.stringify(transformContact(contact), null, 2));
|
|
394
|
+
}
|
|
395
|
+
function outputContactPretty(contact) {
|
|
396
|
+
const name = [contact.firstName, contact.lastName].filter(Boolean).join(" ") || chalk3.gray("(no name)");
|
|
397
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
398
|
+
console.log(`
|
|
399
|
+
${chalk3.bold(name)}`);
|
|
400
|
+
console.log(chalk3.gray(` ID: ${contact.id}`));
|
|
401
|
+
if (contact.email) console.log(` ${chalk3.cyan("Email:")} ${contact.email}`);
|
|
402
|
+
if (contact.phone) console.log(` ${chalk3.cyan("Phone:")} ${contact.phone}`);
|
|
403
|
+
if (contact.telegram) console.log(` ${chalk3.cyan("Telegram:")} ${contact.telegram}`);
|
|
404
|
+
if (contact.linkedIn) console.log(` ${chalk3.cyan("LinkedIn:")} ${contact.linkedIn}`);
|
|
405
|
+
if (contact.twitter) console.log(` ${chalk3.cyan("Twitter:")} ${contact.twitter}`);
|
|
406
|
+
if (contact.github) console.log(` ${chalk3.cyan("GitHub:")} ${contact.github}`);
|
|
407
|
+
if (contact.bluesky) console.log(` ${chalk3.cyan("Bluesky:")} ${contact.bluesky}`);
|
|
408
|
+
if (contact.about) console.log(` ${chalk3.gray("About:")} ${contact.about.substring(0, 120)}${contact.about.length > 120 ? "..." : ""}`);
|
|
409
|
+
if (contact.profileType) console.log(` ${chalk3.magenta("Type:")} ${contact.profileType}`);
|
|
410
|
+
if (contact.skills?.length) console.log(` ${chalk3.yellow("Skills:")} ${contact.skills.join(", ")}`);
|
|
411
|
+
if (contact.tags?.length) console.log(` ${chalk3.yellow("Tags:")} ${contact.tags.join(", ")}`);
|
|
412
|
+
if (contact.organization) console.log(` ${chalk3.green("Org:")} ${contact.organization.name}`);
|
|
413
|
+
if (contact.connectionScore != null) console.log(` ${chalk3.blue("Connection Score:")} ${contact.connectionScore}`);
|
|
414
|
+
if (contact.lastInteractionAt) console.log(` ${chalk3.gray("Last Interaction:")} ${formatDate(new Date(contact.lastInteractionAt))} (${contact.lastInteractionType ?? ""})`);
|
|
415
|
+
console.log();
|
|
416
|
+
}
|
|
417
|
+
function outputContactsJson(contacts, nextCursor) {
|
|
418
|
+
console.log(JSON.stringify({
|
|
419
|
+
contacts: contacts.map(transformContact),
|
|
420
|
+
total: contacts.length,
|
|
421
|
+
nextCursor: nextCursor ?? null
|
|
422
|
+
}, null, 2));
|
|
423
|
+
}
|
|
424
|
+
function outputContactsPretty(contacts) {
|
|
425
|
+
if (contacts.length === 0) {
|
|
426
|
+
console.log(chalk3.gray("No contacts found."));
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
console.log(chalk3.bold(`
|
|
430
|
+
Contacts (${contacts.length} total)`));
|
|
431
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
432
|
+
for (const contact of contacts) {
|
|
433
|
+
const name = [contact.firstName, contact.lastName].filter(Boolean).join(" ") || chalk3.gray("(no name)");
|
|
434
|
+
console.log(`
|
|
435
|
+
${chalk3.bold(name)}`);
|
|
436
|
+
console.log(chalk3.gray(` ID: ${contact.id}`));
|
|
437
|
+
if (contact.email) console.log(` ${chalk3.cyan("Email:")} ${contact.email}`);
|
|
438
|
+
if (contact.organization) console.log(` ${chalk3.green("Org:")} ${contact.organization.name}`);
|
|
439
|
+
if (contact.tags?.length) console.log(` ${chalk3.yellow("Tags:")} ${contact.tags.join(", ")}`);
|
|
440
|
+
}
|
|
441
|
+
console.log();
|
|
442
|
+
}
|
|
443
|
+
function transformContact(contact) {
|
|
444
|
+
return {
|
|
445
|
+
id: contact.id,
|
|
446
|
+
workspaceId: contact.workspaceId,
|
|
447
|
+
firstName: contact.firstName,
|
|
448
|
+
lastName: contact.lastName,
|
|
449
|
+
email: contact.email,
|
|
450
|
+
phone: contact.phone,
|
|
451
|
+
linkedIn: contact.linkedIn,
|
|
452
|
+
telegram: contact.telegram,
|
|
453
|
+
twitter: contact.twitter,
|
|
454
|
+
github: contact.github,
|
|
455
|
+
bluesky: contact.bluesky,
|
|
456
|
+
about: contact.about,
|
|
457
|
+
profileType: contact.profileType,
|
|
458
|
+
skills: contact.skills,
|
|
459
|
+
tags: contact.tags,
|
|
460
|
+
organizationId: contact.organizationId,
|
|
461
|
+
organization: contact.organization ?? null,
|
|
462
|
+
connectionScore: contact.connectionScore,
|
|
463
|
+
lastInteractionAt: contact.lastInteractionAt ? new Date(contact.lastInteractionAt).toISOString() : null,
|
|
464
|
+
lastInteractionType: contact.lastInteractionType,
|
|
465
|
+
createdAt: new Date(contact.createdAt).toISOString(),
|
|
466
|
+
updatedAt: new Date(contact.updatedAt).toISOString()
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function outputInteractionJson(interaction) {
|
|
470
|
+
console.log(JSON.stringify(interaction, null, 2));
|
|
471
|
+
}
|
|
472
|
+
function outputInteractionPretty(interaction) {
|
|
473
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
474
|
+
console.log(` ${chalk3.cyan("Type:")} ${interaction.type} (${interaction.direction})`);
|
|
475
|
+
if (interaction.subject) console.log(` ${chalk3.bold("Subject:")} ${interaction.subject}`);
|
|
476
|
+
if (interaction.notes) console.log(` ${chalk3.gray("Notes:")} ${interaction.notes}`);
|
|
477
|
+
console.log();
|
|
478
|
+
}
|
|
479
|
+
function outputDealJson(deal) {
|
|
480
|
+
console.log(JSON.stringify(transformDeal(deal), null, 2));
|
|
481
|
+
}
|
|
482
|
+
function outputDealPretty(deal) {
|
|
483
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
484
|
+
console.log(`
|
|
485
|
+
${chalk3.bold(deal.title)}`);
|
|
486
|
+
console.log(chalk3.gray(` ID: ${deal.id}`));
|
|
487
|
+
if (deal.stage) console.log(` ${chalk3.blue("Stage:")} ${deal.stage.name}`);
|
|
488
|
+
if (deal.value != null) console.log(` ${chalk3.green("Value:")} ${deal.currency} ${deal.value.toLocaleString()}`);
|
|
489
|
+
if (deal.probability != null) console.log(` ${chalk3.yellow("Probability:")} ${deal.probability}%`);
|
|
490
|
+
if (deal.expectedCloseDate) console.log(` ${chalk3.cyan("Expected Close:")} ${formatDate(new Date(deal.expectedCloseDate))}`);
|
|
491
|
+
if (deal.contact) {
|
|
492
|
+
const name = [deal.contact.firstName, deal.contact.lastName].filter(Boolean).join(" ");
|
|
493
|
+
if (name) console.log(` ${chalk3.magenta("Contact:")} ${name}`);
|
|
494
|
+
}
|
|
495
|
+
if (deal.organization) console.log(` ${chalk3.magenta("Org:")} ${deal.organization.name}`);
|
|
496
|
+
if (deal.assignedTo) console.log(` ${chalk3.gray("Assigned:")} ${deal.assignedTo.name ?? deal.assignedTo.email ?? deal.assignedTo.id}`);
|
|
497
|
+
if (deal.description) console.log(` ${chalk3.gray("Description:")} ${deal.description.substring(0, 120)}${deal.description.length > 120 ? "..." : ""}`);
|
|
498
|
+
console.log();
|
|
499
|
+
}
|
|
500
|
+
function outputDealsJson(deals) {
|
|
501
|
+
console.log(JSON.stringify({
|
|
502
|
+
deals: deals.map(transformDeal),
|
|
503
|
+
total: deals.length
|
|
504
|
+
}, null, 2));
|
|
505
|
+
}
|
|
506
|
+
function outputDealsPretty(deals) {
|
|
507
|
+
if (deals.length === 0) {
|
|
508
|
+
console.log(chalk3.gray("No deals found."));
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
console.log(chalk3.bold(`
|
|
512
|
+
Deals (${deals.length} total)`));
|
|
513
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
514
|
+
for (const deal of deals) {
|
|
515
|
+
const stageLabel = deal.stage ? chalk3.blue(`[${deal.stage.name}]`) : "";
|
|
516
|
+
const valueLabel = deal.value != null ? chalk3.green(` ${deal.currency} ${deal.value.toLocaleString()}`) : "";
|
|
517
|
+
console.log(`
|
|
518
|
+
${stageLabel} ${chalk3.bold(deal.title)}${valueLabel}`);
|
|
519
|
+
console.log(chalk3.gray(` ID: ${deal.id}`));
|
|
520
|
+
if (deal.contact) {
|
|
521
|
+
const name = [deal.contact.firstName, deal.contact.lastName].filter(Boolean).join(" ");
|
|
522
|
+
if (name) console.log(` ${chalk3.magenta("Contact:")} ${name}`);
|
|
523
|
+
}
|
|
524
|
+
if (deal.probability != null) console.log(` ${chalk3.yellow("Probability:")} ${deal.probability}%`);
|
|
525
|
+
if (deal.expectedCloseDate) console.log(` ${chalk3.cyan("Close:")} ${formatDate(new Date(deal.expectedCloseDate))}`);
|
|
526
|
+
}
|
|
527
|
+
console.log();
|
|
528
|
+
}
|
|
529
|
+
function transformDeal(deal) {
|
|
530
|
+
return {
|
|
531
|
+
id: deal.id,
|
|
532
|
+
projectId: deal.projectId,
|
|
533
|
+
stageId: deal.stageId,
|
|
534
|
+
title: deal.title,
|
|
535
|
+
description: deal.description,
|
|
536
|
+
value: deal.value,
|
|
537
|
+
currency: deal.currency,
|
|
538
|
+
probability: deal.probability,
|
|
539
|
+
expectedCloseDate: deal.expectedCloseDate ? new Date(deal.expectedCloseDate).toISOString() : null,
|
|
540
|
+
closedAt: deal.closedAt ? new Date(deal.closedAt).toISOString() : null,
|
|
541
|
+
contactId: deal.contactId,
|
|
542
|
+
organizationId: deal.organizationId,
|
|
543
|
+
workspaceId: deal.workspaceId,
|
|
544
|
+
assignedToId: deal.assignedToId,
|
|
545
|
+
stage: deal.stage ? { id: deal.stage.id, name: deal.stage.name, color: deal.stage.color } : null,
|
|
546
|
+
contact: deal.contact ?? null,
|
|
547
|
+
organization: deal.organization ?? null,
|
|
548
|
+
assignedTo: deal.assignedTo ?? null,
|
|
549
|
+
createdAt: new Date(deal.createdAt).toISOString(),
|
|
550
|
+
updatedAt: new Date(deal.updatedAt).toISOString()
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function outputPipelineJson(pipeline) {
|
|
554
|
+
console.log(JSON.stringify({
|
|
555
|
+
id: pipeline.id,
|
|
556
|
+
name: pipeline.name,
|
|
557
|
+
workspaceId: pipeline.workspaceId,
|
|
558
|
+
status: pipeline.status,
|
|
559
|
+
stages: pipeline.pipelineStages.map((s) => ({
|
|
560
|
+
id: s.id,
|
|
561
|
+
name: s.name,
|
|
562
|
+
color: s.color,
|
|
563
|
+
order: s.order,
|
|
564
|
+
type: s.type,
|
|
565
|
+
dealCount: s._count?.deals ?? null
|
|
566
|
+
}))
|
|
567
|
+
}, null, 2));
|
|
568
|
+
}
|
|
569
|
+
function outputPipelinePretty(pipeline) {
|
|
570
|
+
console.log(chalk3.bold(`
|
|
571
|
+
Pipeline: ${pipeline.name}`));
|
|
572
|
+
console.log(chalk3.gray(` ID: ${pipeline.id}`));
|
|
573
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
574
|
+
for (const stage of pipeline.pipelineStages) {
|
|
575
|
+
const count = stage._count?.deals != null ? chalk3.gray(` (${stage._count.deals} deals)`) : "";
|
|
576
|
+
console.log(` ${chalk3.bold(stage.name)}${count} \u2014 ${chalk3.gray(stage.type)}`);
|
|
577
|
+
}
|
|
578
|
+
console.log();
|
|
579
|
+
}
|
|
580
|
+
function outputStagesJson(stages) {
|
|
581
|
+
console.log(JSON.stringify({
|
|
582
|
+
stages: stages.map((s) => ({
|
|
583
|
+
id: s.id,
|
|
584
|
+
name: s.name,
|
|
585
|
+
color: s.color,
|
|
586
|
+
order: s.order,
|
|
587
|
+
type: s.type,
|
|
588
|
+
dealCount: s._count?.deals ?? null
|
|
589
|
+
})),
|
|
590
|
+
total: stages.length
|
|
591
|
+
}, null, 2));
|
|
592
|
+
}
|
|
593
|
+
function transformComment(comment) {
|
|
594
|
+
const parentKey = "actionId" in comment ? "actionId" : "ticketId";
|
|
595
|
+
const parentId = comment[parentKey];
|
|
596
|
+
return {
|
|
597
|
+
id: comment.id,
|
|
598
|
+
[parentKey]: parentId,
|
|
599
|
+
authorId: comment.authorId,
|
|
600
|
+
content: comment.content,
|
|
601
|
+
author: comment.author,
|
|
602
|
+
createdAt: new Date(comment.createdAt).toISOString(),
|
|
603
|
+
updatedAt: new Date(comment.updatedAt).toISOString()
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
function outputCommentJson(comment) {
|
|
607
|
+
console.log(JSON.stringify(transformComment(comment), null, 2));
|
|
608
|
+
}
|
|
609
|
+
function outputCommentPretty(comment) {
|
|
610
|
+
const author = comment.author?.name ?? comment.authorId;
|
|
611
|
+
const when = formatDate(new Date(comment.createdAt));
|
|
612
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
613
|
+
console.log(`${chalk3.bold(author)} ${chalk3.gray(`\u2014 ${when}`)}`);
|
|
614
|
+
console.log(chalk3.gray(` ID: ${comment.id}`));
|
|
615
|
+
console.log();
|
|
616
|
+
console.log(comment.content);
|
|
617
|
+
console.log();
|
|
618
|
+
}
|
|
619
|
+
function outputCommentsJson(comments) {
|
|
620
|
+
console.log(JSON.stringify({
|
|
621
|
+
comments: comments.map(transformComment),
|
|
622
|
+
total: comments.length
|
|
623
|
+
}, null, 2));
|
|
624
|
+
}
|
|
625
|
+
function outputCommentsPretty(comments) {
|
|
626
|
+
if (comments.length === 0) {
|
|
627
|
+
console.log(chalk3.gray("No comments."));
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
console.log(chalk3.bold(`
|
|
631
|
+
Comments (${comments.length})`));
|
|
632
|
+
for (const c of comments) {
|
|
633
|
+
outputCommentPretty(c);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function outputProductJson(product) {
|
|
637
|
+
console.log(JSON.stringify(transformProduct(product), null, 2));
|
|
638
|
+
}
|
|
639
|
+
function outputProductPretty(product) {
|
|
640
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
641
|
+
console.log(`
|
|
642
|
+
${chalk3.bold(product.name)} ${chalk3.gray(`(${product.slug})`)}`);
|
|
643
|
+
console.log(chalk3.gray(` ID: ${product.id}`));
|
|
644
|
+
if (product.description) {
|
|
645
|
+
console.log(` ${chalk3.gray("Description:")} ${product.description}`);
|
|
646
|
+
}
|
|
647
|
+
if (product._count) {
|
|
648
|
+
const c = product._count;
|
|
649
|
+
const parts = [
|
|
650
|
+
c.features != null ? `features: ${c.features}` : null,
|
|
651
|
+
c.tickets != null ? `tickets: ${c.tickets}` : null
|
|
652
|
+
].filter(Boolean);
|
|
653
|
+
if (parts.length) console.log(` ${chalk3.cyan("Counts:")} ${parts.join(" ")}`);
|
|
654
|
+
}
|
|
655
|
+
console.log();
|
|
656
|
+
}
|
|
657
|
+
function outputProductsJson(products) {
|
|
658
|
+
console.log(JSON.stringify({
|
|
659
|
+
products: products.map(transformProduct),
|
|
660
|
+
total: products.length
|
|
661
|
+
}, null, 2));
|
|
662
|
+
}
|
|
663
|
+
function outputProductsPretty(products) {
|
|
664
|
+
if (products.length === 0) {
|
|
665
|
+
console.log(chalk3.gray("No products found."));
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
console.log(chalk3.bold(`
|
|
669
|
+
Products (${products.length} total)`));
|
|
670
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
671
|
+
for (const p of products) {
|
|
672
|
+
const ticketCount = p._count?.tickets != null ? chalk3.gray(` \u2014 ${p._count.tickets} tickets`) : "";
|
|
673
|
+
console.log(` ${chalk3.bold(p.name)} ${chalk3.gray(`(${p.slug})`)}${ticketCount}`);
|
|
674
|
+
console.log(chalk3.gray(` ID: ${p.id}`));
|
|
675
|
+
}
|
|
676
|
+
console.log();
|
|
677
|
+
}
|
|
678
|
+
function transformProduct(product) {
|
|
679
|
+
return {
|
|
680
|
+
id: product.id,
|
|
681
|
+
workspaceId: product.workspaceId,
|
|
682
|
+
name: product.name,
|
|
683
|
+
slug: product.slug,
|
|
684
|
+
description: product.description,
|
|
685
|
+
icon: product.icon,
|
|
686
|
+
color: product.color,
|
|
687
|
+
funTicketIds: product.funTicketIds,
|
|
688
|
+
ticketCounter: product.ticketCounter,
|
|
689
|
+
counts: product._count ?? null,
|
|
690
|
+
createdAt: new Date(product.createdAt).toISOString(),
|
|
691
|
+
updatedAt: new Date(product.updatedAt).toISOString()
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function outputFeatureJson(feature) {
|
|
695
|
+
console.log(JSON.stringify(transformFeature(feature), null, 2));
|
|
696
|
+
}
|
|
697
|
+
function outputFeaturePretty(feature) {
|
|
698
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
699
|
+
console.log(`
|
|
700
|
+
${chalk3[getFeatureStatusColor(feature.status)](`[${feature.status}]`)} ${chalk3.bold(feature.name)}`);
|
|
701
|
+
console.log(chalk3.gray(` ID: ${feature.id}`));
|
|
702
|
+
if (feature.description) {
|
|
703
|
+
console.log(` ${chalk3.gray("Description:")} ${feature.description.substring(0, 120)}${feature.description.length > 120 ? "..." : ""}`);
|
|
704
|
+
}
|
|
705
|
+
if (feature.vision) {
|
|
706
|
+
console.log(` ${chalk3.gray("Vision:")} ${feature.vision.substring(0, 120)}${feature.vision.length > 120 ? "..." : ""}`);
|
|
707
|
+
}
|
|
708
|
+
if (feature.priority != null) {
|
|
709
|
+
console.log(` ${chalk3.magenta("Priority:")} ${feature.priority}`);
|
|
710
|
+
}
|
|
711
|
+
if (feature._count?.tickets != null) {
|
|
712
|
+
console.log(` ${chalk3.cyan("Tickets:")} ${feature._count.tickets}`);
|
|
713
|
+
}
|
|
714
|
+
console.log();
|
|
715
|
+
}
|
|
716
|
+
function outputFeaturesJson(features) {
|
|
717
|
+
console.log(JSON.stringify({
|
|
718
|
+
features: features.map(transformFeature),
|
|
719
|
+
total: features.length
|
|
720
|
+
}, null, 2));
|
|
721
|
+
}
|
|
722
|
+
function outputFeaturesPretty(features) {
|
|
723
|
+
if (features.length === 0) {
|
|
724
|
+
console.log(chalk3.gray("No features found."));
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
console.log(chalk3.bold(`
|
|
728
|
+
Features (${features.length} total)`));
|
|
729
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
730
|
+
for (const f of features) {
|
|
731
|
+
const statusBadge = chalk3[getFeatureStatusColor(f.status)](`[${f.status}]`);
|
|
732
|
+
const ticketCount = f._count?.tickets != null ? chalk3.gray(` \u2014 ${f._count.tickets} tickets`) : "";
|
|
733
|
+
console.log(` ${statusBadge} ${chalk3.bold(f.name)}${ticketCount}`);
|
|
734
|
+
console.log(chalk3.gray(` ID: ${f.id}`));
|
|
735
|
+
}
|
|
736
|
+
console.log();
|
|
737
|
+
}
|
|
738
|
+
function transformFeature(feature) {
|
|
739
|
+
return {
|
|
740
|
+
id: feature.id,
|
|
741
|
+
productId: feature.productId,
|
|
742
|
+
name: feature.name,
|
|
743
|
+
description: feature.description,
|
|
744
|
+
vision: feature.vision,
|
|
745
|
+
status: feature.status,
|
|
746
|
+
effort: feature.effort,
|
|
747
|
+
priority: feature.priority,
|
|
748
|
+
goalId: feature.goalId,
|
|
749
|
+
goal: feature.goal ?? null,
|
|
750
|
+
counts: feature._count ?? null,
|
|
751
|
+
createdAt: new Date(feature.createdAt).toISOString(),
|
|
752
|
+
updatedAt: new Date(feature.updatedAt).toISOString()
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function getFeatureStatusColor(status) {
|
|
756
|
+
switch (status) {
|
|
757
|
+
case "IDEA":
|
|
758
|
+
return "gray";
|
|
759
|
+
case "DEFINED":
|
|
760
|
+
return "blue";
|
|
761
|
+
case "IN_PROGRESS":
|
|
762
|
+
return "yellow";
|
|
763
|
+
case "SHIPPED":
|
|
764
|
+
return "green";
|
|
765
|
+
case "ARCHIVED":
|
|
766
|
+
return "red";
|
|
767
|
+
default:
|
|
768
|
+
return "gray";
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function outputTicketJson(ticket) {
|
|
772
|
+
console.log(JSON.stringify(transformTicket(ticket), null, 2));
|
|
773
|
+
}
|
|
774
|
+
function outputTicketPretty(ticket) {
|
|
775
|
+
const id = ticket.shortId ?? (ticket.number != null ? `#${ticket.number}` : "?");
|
|
776
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
777
|
+
console.log(
|
|
778
|
+
`
|
|
779
|
+
${chalk3[getTicketStatusColor(ticket.status)](`[${ticket.status}]`)} ${chalk3.gray(`[${ticket.type}]`)} ${chalk3.bold(`${id} ${ticket.title}`)}`
|
|
780
|
+
);
|
|
781
|
+
console.log(chalk3.gray(` ID: ${ticket.id}`));
|
|
782
|
+
if (ticket.isBlocked) {
|
|
783
|
+
console.log(` ${chalk3.red("BLOCKED")} by ${ticket.openBlockerCount ?? 0} open ticket(s)`);
|
|
784
|
+
} else if (ticket.openBlockerCount && ticket.openBlockerCount > 0) {
|
|
785
|
+
console.log(` ${chalk3.yellow("Blockers:")} ${ticket.openBlockerCount} open (not currently in flight)`);
|
|
786
|
+
}
|
|
787
|
+
if (ticket.feature) {
|
|
788
|
+
console.log(` ${chalk3.cyan("Feature:")} ${ticket.feature.name}`);
|
|
789
|
+
}
|
|
790
|
+
if (ticket.epic) {
|
|
791
|
+
console.log(` ${chalk3.cyan("Epic:")} ${ticket.epic.name}`);
|
|
792
|
+
}
|
|
793
|
+
if (ticket.assignee) {
|
|
794
|
+
console.log(` ${chalk3.magenta("Assignee:")} ${ticket.assignee.name ?? ticket.assignee.id}`);
|
|
795
|
+
}
|
|
796
|
+
if (ticket.priority != null) {
|
|
797
|
+
console.log(` ${chalk3.magenta("Priority:")} ${ticket.priority}`);
|
|
798
|
+
}
|
|
799
|
+
if (ticket.points != null) {
|
|
800
|
+
console.log(` ${chalk3.yellow("Points:")} ${ticket.points}`);
|
|
801
|
+
}
|
|
802
|
+
if (ticket.body) {
|
|
803
|
+
console.log(` ${chalk3.gray("Body:")} ${ticket.body.substring(0, 200)}${ticket.body.length > 200 ? "..." : ""}`);
|
|
804
|
+
}
|
|
805
|
+
const detail = ticket;
|
|
806
|
+
if (detail.dependsOn && detail.dependsOn.length > 0) {
|
|
807
|
+
console.log(` ${chalk3.cyan("Depends on:")}`);
|
|
808
|
+
for (const dep of detail.dependsOn) {
|
|
809
|
+
const depId = dep.shortId ?? (dep.number != null ? `#${dep.number}` : "?");
|
|
810
|
+
console.log(` - ${chalk3.gray(`[${dep.status}]`)} ${depId} ${dep.title}`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
if (detail.requiredFor && detail.requiredFor.length > 0) {
|
|
814
|
+
console.log(` ${chalk3.cyan("Required for:")}`);
|
|
815
|
+
for (const dep of detail.requiredFor) {
|
|
816
|
+
const depId = dep.shortId ?? (dep.number != null ? `#${dep.number}` : "?");
|
|
817
|
+
console.log(` - ${chalk3.gray(`[${dep.status}]`)} ${depId} ${dep.title}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (detail.actions && detail.actions.length > 0) {
|
|
821
|
+
console.log(` ${chalk3.cyan("Actions:")} ${detail.actions.length}`);
|
|
822
|
+
for (const a of detail.actions) {
|
|
823
|
+
console.log(` - ${chalk3.gray(`[${a.kanbanStatus ?? a.status}]`)} ${a.name} ${chalk3.gray(`(${a.id})`)}`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
console.log();
|
|
827
|
+
}
|
|
828
|
+
function outputTicketsJson(tickets) {
|
|
829
|
+
console.log(JSON.stringify({
|
|
830
|
+
tickets: tickets.map(transformTicket),
|
|
831
|
+
total: tickets.length
|
|
832
|
+
}, null, 2));
|
|
833
|
+
}
|
|
834
|
+
function outputTicketsPretty(tickets) {
|
|
835
|
+
if (tickets.length === 0) {
|
|
836
|
+
console.log(chalk3.gray("No tickets found."));
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
console.log(chalk3.bold(`
|
|
840
|
+
Tickets (${tickets.length} total)`));
|
|
841
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
842
|
+
for (const t of tickets) {
|
|
843
|
+
const id = t.shortId ?? (t.number != null ? `#${t.number}` : "?");
|
|
844
|
+
const statusBadge = chalk3[getTicketStatusColor(t.status)](`[${t.status}]`);
|
|
845
|
+
const typeBadge = chalk3.gray(`[${t.type}]`);
|
|
846
|
+
const blocked = t.isBlocked ? chalk3.red(" BLOCKED") : "";
|
|
847
|
+
console.log(` ${statusBadge} ${typeBadge} ${chalk3.bold(id)} ${t.title}${blocked}`);
|
|
848
|
+
console.log(chalk3.gray(` ID: ${t.id}`));
|
|
849
|
+
}
|
|
850
|
+
console.log();
|
|
851
|
+
}
|
|
852
|
+
function transformTicket(ticket) {
|
|
853
|
+
const detail = ticket;
|
|
854
|
+
return {
|
|
855
|
+
id: ticket.id,
|
|
856
|
+
productId: ticket.productId,
|
|
857
|
+
number: ticket.number,
|
|
858
|
+
shortId: ticket.shortId,
|
|
859
|
+
title: ticket.title,
|
|
860
|
+
body: ticket.body,
|
|
861
|
+
type: ticket.type,
|
|
862
|
+
status: ticket.status,
|
|
863
|
+
priority: ticket.priority,
|
|
864
|
+
points: ticket.points,
|
|
865
|
+
branchName: ticket.branchName,
|
|
866
|
+
prUrl: ticket.prUrl,
|
|
867
|
+
designUrl: ticket.designUrl,
|
|
868
|
+
specUrl: ticket.specUrl,
|
|
869
|
+
links: ticket.links,
|
|
870
|
+
epicId: ticket.epicId,
|
|
871
|
+
featureId: ticket.featureId,
|
|
872
|
+
cycleId: ticket.cycleId,
|
|
873
|
+
scopeId: ticket.scopeId,
|
|
874
|
+
assigneeId: ticket.assigneeId,
|
|
875
|
+
assignee: ticket.assignee ?? null,
|
|
876
|
+
feature: ticket.feature ?? null,
|
|
877
|
+
epic: ticket.epic ?? null,
|
|
878
|
+
openBlockerCount: ticket.openBlockerCount ?? 0,
|
|
879
|
+
isBlocked: ticket.isBlocked ?? false,
|
|
880
|
+
counts: ticket._count ?? null,
|
|
881
|
+
dependsOn: detail.dependsOn ?? void 0,
|
|
882
|
+
requiredFor: detail.requiredFor ?? void 0,
|
|
883
|
+
actions: detail.actions?.map((a) => ({
|
|
884
|
+
id: a.id,
|
|
885
|
+
name: a.name,
|
|
886
|
+
status: a.status,
|
|
887
|
+
kanbanStatus: a.kanbanStatus,
|
|
888
|
+
completedAt: a.completedAt ? new Date(a.completedAt).toISOString() : null
|
|
889
|
+
})),
|
|
890
|
+
completedAt: ticket.completedAt ? new Date(ticket.completedAt).toISOString() : null,
|
|
891
|
+
createdAt: new Date(ticket.createdAt).toISOString(),
|
|
892
|
+
updatedAt: new Date(ticket.updatedAt).toISOString()
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function getTicketStatusColor(status) {
|
|
896
|
+
switch (status) {
|
|
897
|
+
case "BACKLOG":
|
|
898
|
+
return "gray";
|
|
899
|
+
case "NEEDS_REFINEMENT":
|
|
900
|
+
return "gray";
|
|
901
|
+
case "READY_TO_PLAN":
|
|
902
|
+
return "blue";
|
|
903
|
+
case "COMMITTED":
|
|
904
|
+
return "blue";
|
|
905
|
+
case "IN_PROGRESS":
|
|
906
|
+
return "yellow";
|
|
907
|
+
case "BLOCKED":
|
|
908
|
+
return "red";
|
|
909
|
+
case "QA":
|
|
910
|
+
return "cyan";
|
|
911
|
+
case "DONE":
|
|
912
|
+
return "green";
|
|
913
|
+
case "DEPLOYED":
|
|
914
|
+
return "green";
|
|
915
|
+
case "ARCHIVED":
|
|
916
|
+
return "gray";
|
|
917
|
+
default:
|
|
918
|
+
return "gray";
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function outputEpicJson(epic) {
|
|
922
|
+
console.log(JSON.stringify(transformEpic(epic), null, 2));
|
|
923
|
+
}
|
|
924
|
+
function outputEpicPretty(epic) {
|
|
925
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
926
|
+
console.log(`
|
|
927
|
+
${chalk3[getEpicStatusColor(epic.status)](`[${epic.status}]`)} ${chalk3.bold(epic.name)}`);
|
|
928
|
+
console.log(chalk3.gray(` ID: ${epic.id}`));
|
|
929
|
+
if (epic.description) {
|
|
930
|
+
console.log(` ${chalk3.gray("Description:")} ${epic.description.substring(0, 200)}${epic.description.length > 200 ? "..." : ""}`);
|
|
931
|
+
}
|
|
932
|
+
console.log(` ${chalk3.magenta("Priority:")} ${epic.priority}`);
|
|
933
|
+
if (epic.startDate) {
|
|
934
|
+
console.log(` ${chalk3.cyan("Start:")} ${formatDate(new Date(epic.startDate))}`);
|
|
935
|
+
}
|
|
936
|
+
if (epic.targetDate) {
|
|
937
|
+
console.log(` ${chalk3.cyan("Target:")} ${formatDate(new Date(epic.targetDate))}`);
|
|
938
|
+
}
|
|
939
|
+
if (epic.owner) {
|
|
940
|
+
console.log(` ${chalk3.gray("Owner:")} ${epic.owner.name ?? epic.owner.email ?? epic.owner.id}`);
|
|
941
|
+
}
|
|
942
|
+
if (epic._count) {
|
|
943
|
+
const c = epic._count;
|
|
944
|
+
const parts = [
|
|
945
|
+
c.actions != null ? `actions: ${c.actions}` : null,
|
|
946
|
+
c.tickets != null ? `tickets: ${c.tickets}` : null
|
|
947
|
+
].filter(Boolean);
|
|
948
|
+
if (parts.length) console.log(` ${chalk3.cyan("Counts:")} ${parts.join(" ")}`);
|
|
949
|
+
}
|
|
950
|
+
console.log();
|
|
951
|
+
}
|
|
952
|
+
function outputEpicsJson(epics) {
|
|
953
|
+
console.log(JSON.stringify({
|
|
954
|
+
epics: epics.map(transformEpic),
|
|
955
|
+
total: epics.length
|
|
956
|
+
}, null, 2));
|
|
957
|
+
}
|
|
958
|
+
function outputEpicsPretty(epics) {
|
|
959
|
+
if (epics.length === 0) {
|
|
960
|
+
console.log(chalk3.gray("No epics found."));
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
console.log(chalk3.bold(`
|
|
964
|
+
Epics (${epics.length} total)`));
|
|
965
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
966
|
+
for (const e of epics) {
|
|
967
|
+
const statusBadge = chalk3[getEpicStatusColor(e.status)](`[${e.status}]`);
|
|
968
|
+
const counts = e._count ? chalk3.gray(
|
|
969
|
+
` \u2014 ${e._count.tickets ?? 0} tickets, ${e._count.actions ?? 0} actions`
|
|
970
|
+
) : "";
|
|
971
|
+
console.log(` ${statusBadge} ${chalk3.bold(e.name)}${counts}`);
|
|
972
|
+
console.log(chalk3.gray(` ID: ${e.id}`));
|
|
973
|
+
}
|
|
974
|
+
console.log();
|
|
975
|
+
}
|
|
976
|
+
function transformEpic(epic) {
|
|
977
|
+
return {
|
|
978
|
+
id: epic.id,
|
|
979
|
+
workspaceId: epic.workspaceId,
|
|
980
|
+
name: epic.name,
|
|
981
|
+
description: epic.description,
|
|
982
|
+
status: epic.status,
|
|
983
|
+
priority: epic.priority,
|
|
984
|
+
startDate: epic.startDate ? new Date(epic.startDate).toISOString() : null,
|
|
985
|
+
targetDate: epic.targetDate ? new Date(epic.targetDate).toISOString() : null,
|
|
986
|
+
ownerId: epic.ownerId,
|
|
987
|
+
owner: epic.owner ?? null,
|
|
988
|
+
counts: epic._count ?? null,
|
|
989
|
+
createdAt: new Date(epic.createdAt).toISOString(),
|
|
990
|
+
updatedAt: new Date(epic.updatedAt).toISOString()
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
function getEpicStatusColor(status) {
|
|
994
|
+
switch (status) {
|
|
995
|
+
case "OPEN":
|
|
996
|
+
return "blue";
|
|
997
|
+
case "IN_PROGRESS":
|
|
998
|
+
return "yellow";
|
|
999
|
+
case "DONE":
|
|
1000
|
+
return "green";
|
|
1001
|
+
case "CANCELLED":
|
|
1002
|
+
return "red";
|
|
1003
|
+
default:
|
|
1004
|
+
return "gray";
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function outputStagesPretty(stages) {
|
|
1008
|
+
if (stages.length === 0) {
|
|
1009
|
+
console.log(chalk3.gray("No stages found."));
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
console.log(chalk3.bold(`
|
|
1013
|
+
Pipeline Stages (${stages.length})`));
|
|
1014
|
+
console.log(chalk3.gray("\u2500".repeat(50)));
|
|
1015
|
+
for (const stage of stages) {
|
|
1016
|
+
const count = stage._count?.deals != null ? chalk3.gray(` (${stage._count.deals} deals)`) : "";
|
|
1017
|
+
console.log(` ${chalk3.bold(stage.name)}${count} \u2014 ${chalk3.gray(stage.type)}`);
|
|
1018
|
+
}
|
|
1019
|
+
console.log();
|
|
1020
|
+
}
|
|
368
1021
|
|
|
369
1022
|
// src/commands/actions.ts
|
|
1023
|
+
var VALID_PRIORITIES = [
|
|
1024
|
+
"Quick",
|
|
1025
|
+
"Scheduled",
|
|
1026
|
+
"1st Priority",
|
|
1027
|
+
"2nd Priority",
|
|
1028
|
+
"3rd Priority",
|
|
1029
|
+
"4th Priority",
|
|
1030
|
+
"5th Priority",
|
|
1031
|
+
"Errand",
|
|
1032
|
+
"Remember",
|
|
1033
|
+
"Watch",
|
|
1034
|
+
"Someday Maybe"
|
|
1035
|
+
];
|
|
370
1036
|
function createActionsCommand() {
|
|
371
1037
|
const actions = new Command2("actions").description("Manage actions/tasks");
|
|
372
1038
|
actions.command("list").description("List all actions").option("--project <id>", "Filter by project ID").option("--status <status>", "Filter by kanban status (BACKLOG, TODO, IN_PROGRESS, IN_REVIEW, DONE)").option("--assignee <id>", "Filter by assignee ID").action(async (options, cmd) => {
|
|
@@ -450,39 +1116,1173 @@ function createActionsCommand() {
|
|
|
450
1116
|
handleError(error, useJson);
|
|
451
1117
|
}
|
|
452
1118
|
});
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// src/commands/projects.ts
|
|
457
|
-
import { Command as Command3 } from "commander";
|
|
458
|
-
function createProjectsCommand() {
|
|
459
|
-
const projects = new Command3("projects").description("Manage projects");
|
|
460
|
-
projects.command("list").description("List all projects").option("--workspace <id>", "Filter by workspace ID").option("--include-actions", "Include actions in output").action(async (options, cmd) => {
|
|
1119
|
+
actions.command("update").description("Update an existing action").requiredOption("--id <id>", "Action ID to update").option("-n, --name <name>", "New action name").option("-d, --description <text>", "New description").option("-p, --project <id>", "Move to project ID").option("--priority <priority>", "Priority (Quick, Scheduled, 1st Priority, etc.)").option("--status <status>", "Status (ACTIVE, COMPLETED, CANCELLED)").option("--kanban <status>", "Kanban status (BACKLOG, TODO, IN_PROGRESS, IN_REVIEW, DONE)").option("--due <date>", 'Due date (YYYY-MM-DD or "null" to clear)').action(async (options, cmd) => {
|
|
461
1120
|
const globalOpts = cmd.optsWithGlobals();
|
|
462
1121
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
463
1122
|
try {
|
|
1123
|
+
if (options.priority && !VALID_PRIORITIES.includes(options.priority)) {
|
|
1124
|
+
throw new Error(`Invalid priority "${options.priority}". Valid values: ${VALID_PRIORITIES.join(", ")}`);
|
|
1125
|
+
}
|
|
1126
|
+
let dueDate;
|
|
1127
|
+
if (options.due === "null") {
|
|
1128
|
+
dueDate = null;
|
|
1129
|
+
} else if (options.due) {
|
|
1130
|
+
dueDate = new Date(options.due);
|
|
1131
|
+
if (isNaN(dueDate.getTime())) {
|
|
1132
|
+
throw new Error("Invalid due date format. Use YYYY-MM-DD");
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
464
1135
|
const client = getClient();
|
|
465
|
-
const
|
|
466
|
-
|
|
467
|
-
|
|
1136
|
+
const action = await client.actions.update({
|
|
1137
|
+
id: options.id,
|
|
1138
|
+
name: options.name,
|
|
1139
|
+
description: options.description,
|
|
1140
|
+
projectId: options.project,
|
|
1141
|
+
priority: options.priority,
|
|
1142
|
+
status: options.status,
|
|
1143
|
+
kanbanStatus: options.kanban,
|
|
1144
|
+
dueDate
|
|
468
1145
|
});
|
|
469
1146
|
if (useJson) {
|
|
470
|
-
|
|
1147
|
+
outputActionJson(action);
|
|
471
1148
|
} else {
|
|
472
|
-
|
|
1149
|
+
console.log("\n\u2713 Action updated successfully");
|
|
1150
|
+
outputActionPretty(action);
|
|
473
1151
|
}
|
|
474
1152
|
} catch (error) {
|
|
475
1153
|
handleError(error, useJson);
|
|
476
1154
|
}
|
|
477
1155
|
});
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
1156
|
+
actions.command("create").description("Create a new action").requiredOption("-n, --name <name>", "Action name/title").option("-d, --description <text>", "Action description").option("-p, --project <id>", "Project ID to assign the action to").option("--priority <priority>", "Priority (Quick, Scheduled, 1st Priority, etc.)").option("--due <date>", "Due date (YYYY-MM-DD)").option("--effort <minutes>", "Effort estimate in minutes", parseInt).option("--ticket <id>", "Link action to a product ticket (CUID) after creation").option("--epic <id>", "Epic CUID to attach the action to").action(async (options, cmd) => {
|
|
1157
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1158
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1159
|
+
try {
|
|
1160
|
+
if (options.priority && !VALID_PRIORITIES.includes(options.priority)) {
|
|
1161
|
+
throw new Error(`Invalid priority "${options.priority}". Valid values: ${VALID_PRIORITIES.join(", ")}`);
|
|
1162
|
+
}
|
|
1163
|
+
let dueDate;
|
|
1164
|
+
if (options.due) {
|
|
1165
|
+
dueDate = new Date(options.due);
|
|
1166
|
+
if (isNaN(dueDate.getTime())) {
|
|
1167
|
+
throw new Error("Invalid due date format. Use YYYY-MM-DD");
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
const client = getClient();
|
|
1171
|
+
let action = await client.actions.create({
|
|
1172
|
+
name: options.name,
|
|
1173
|
+
description: options.description,
|
|
1174
|
+
projectId: options.project,
|
|
1175
|
+
priority: options.priority,
|
|
1176
|
+
dueDate,
|
|
1177
|
+
effortEstimate: options.effort,
|
|
1178
|
+
epicId: options.epic
|
|
1179
|
+
});
|
|
1180
|
+
if (options.ticket) {
|
|
1181
|
+
action = await client.tickets.linkAction(options.ticket, action.id);
|
|
1182
|
+
}
|
|
1183
|
+
if (useJson) {
|
|
1184
|
+
outputActionJson(action);
|
|
1185
|
+
} else {
|
|
1186
|
+
console.log("\n\u2713 Action created successfully");
|
|
1187
|
+
outputActionPretty(action);
|
|
1188
|
+
}
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
handleError(error, useJson);
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
const comment = new Command2("comment").description("Manage comments on an action");
|
|
1194
|
+
comment.command("list").description("List comments on an action").requiredOption("--id <id>", "Action ID").action(async (options, cmd) => {
|
|
1195
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1196
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1197
|
+
try {
|
|
1198
|
+
const client = getClient();
|
|
1199
|
+
const comments = await client.actionComments.list(options.id);
|
|
1200
|
+
if (useJson) outputCommentsJson(comments);
|
|
1201
|
+
else outputCommentsPretty(comments);
|
|
1202
|
+
} catch (error) {
|
|
1203
|
+
handleError(error, useJson);
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
comment.command("add").description("Add a comment to an action").requiredOption("--id <id>", "Action ID").requiredOption("-m, --message <text>", "Comment content (markdown supported)").action(async (options, cmd) => {
|
|
1207
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1208
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1209
|
+
try {
|
|
1210
|
+
const client = getClient();
|
|
1211
|
+
const created = await client.actionComments.add({
|
|
1212
|
+
actionId: options.id,
|
|
1213
|
+
content: options.message
|
|
1214
|
+
});
|
|
1215
|
+
if (useJson) {
|
|
1216
|
+
outputCommentJson(created);
|
|
1217
|
+
} else {
|
|
1218
|
+
console.log("\n\u2713 Comment added");
|
|
1219
|
+
outputCommentPretty(created);
|
|
1220
|
+
}
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
handleError(error, useJson);
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
comment.command("update").description("Update one of your own comments").requiredOption("--comment-id <id>", "Comment ID").requiredOption("-m, --message <text>", "New comment content").action(async (options, cmd) => {
|
|
1226
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1227
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1228
|
+
try {
|
|
1229
|
+
const client = getClient();
|
|
1230
|
+
const updated = await client.actionComments.update({
|
|
1231
|
+
commentId: options.commentId,
|
|
1232
|
+
content: options.message
|
|
1233
|
+
});
|
|
1234
|
+
if (useJson) {
|
|
1235
|
+
outputCommentJson(updated);
|
|
1236
|
+
} else {
|
|
1237
|
+
console.log("\n\u2713 Comment updated");
|
|
1238
|
+
outputCommentPretty(updated);
|
|
1239
|
+
}
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
handleError(error, useJson);
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
comment.command("delete").description("Delete one of your own comments").requiredOption("--comment-id <id>", "Comment ID").action(async (options, cmd) => {
|
|
1245
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1246
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1247
|
+
try {
|
|
1248
|
+
const client = getClient();
|
|
1249
|
+
await client.actionComments.delete(options.commentId);
|
|
1250
|
+
if (useJson) {
|
|
1251
|
+
console.log(JSON.stringify({ deleted: true, id: options.commentId }, null, 2));
|
|
1252
|
+
} else {
|
|
1253
|
+
console.log("\n\u2713 Comment deleted");
|
|
1254
|
+
}
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
handleError(error, useJson);
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
actions.addCommand(comment);
|
|
1260
|
+
return actions;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// src/commands/contacts.ts
|
|
1264
|
+
import { Command as Command3 } from "commander";
|
|
1265
|
+
var VALID_INTERACTION_TYPES = [
|
|
1266
|
+
"EMAIL",
|
|
1267
|
+
"TELEGRAM",
|
|
1268
|
+
"PHONE_CALL",
|
|
1269
|
+
"MEETING",
|
|
1270
|
+
"NOTE",
|
|
1271
|
+
"LINKEDIN",
|
|
1272
|
+
"OTHER"
|
|
1273
|
+
];
|
|
1274
|
+
var VALID_INTERACTION_DIRECTIONS = ["INBOUND", "OUTBOUND"];
|
|
1275
|
+
function createContactsCommand() {
|
|
1276
|
+
const contacts = new Command3("contacts").description("Manage CRM contacts");
|
|
1277
|
+
contacts.command("list").description("List contacts").requiredOption("--workspace <id>", "Workspace ID").option("--search <query>", "Search by name or email").option("--tags <tags>", "Filter by tags (comma-separated)").option("--organization <id>", "Filter by organization ID").option("--limit <n>", "Max results to return", parseInt).option("--cursor <cursor>", "Pagination cursor").action(async (options, cmd) => {
|
|
1278
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1279
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1280
|
+
try {
|
|
1281
|
+
const client = getClient();
|
|
1282
|
+
const result = await client.contacts.list({
|
|
1283
|
+
workspaceId: options.workspace,
|
|
1284
|
+
search: options.search,
|
|
1285
|
+
tags: options.tags?.split(",").map((t) => t.trim()),
|
|
1286
|
+
organizationId: options.organization,
|
|
1287
|
+
limit: options.limit,
|
|
1288
|
+
cursor: options.cursor
|
|
1289
|
+
});
|
|
1290
|
+
if (useJson) {
|
|
1291
|
+
outputContactsJson(result.contacts, result.nextCursor);
|
|
1292
|
+
} else {
|
|
1293
|
+
outputContactsPretty(result.contacts);
|
|
1294
|
+
}
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
handleError(error, useJson);
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
contacts.command("get").description("Get a contact by ID").argument("<id>", "Contact ID").option("--interactions", "Include interaction history").action(async (id, options, cmd) => {
|
|
1300
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1301
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1302
|
+
try {
|
|
1303
|
+
const client = getClient();
|
|
1304
|
+
const contact = await client.contacts.get(id, {
|
|
1305
|
+
includeInteractions: options.interactions
|
|
1306
|
+
});
|
|
1307
|
+
if (useJson) {
|
|
1308
|
+
outputContactJson(contact);
|
|
1309
|
+
} else {
|
|
1310
|
+
outputContactPretty(contact);
|
|
1311
|
+
}
|
|
1312
|
+
} catch (error) {
|
|
1313
|
+
handleError(error, useJson);
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
contacts.command("create").description("Create a new contact").requiredOption("--workspace <id>", "Workspace ID").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--email <email>", "Email address").option("--phone <phone>", "Phone number").option("--linkedin <url>", "LinkedIn URL").option("--telegram <handle>", "Telegram handle").option("--twitter <handle>", "Twitter/X handle").option("--github <handle>", "GitHub username").option("--bluesky <handle>", "Bluesky handle").option("--about <text>", "About / bio").option("--profile-type <type>", "Profile type").option("--skills <skills>", "Skills (comma-separated)").option("--tags <tags>", "Tags (comma-separated)").option("--organization <id>", "Organization ID").action(async (options, cmd) => {
|
|
1317
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1318
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1319
|
+
try {
|
|
1320
|
+
const client = getClient();
|
|
1321
|
+
const contact = await client.contacts.create({
|
|
1322
|
+
workspaceId: options.workspace,
|
|
1323
|
+
firstName: options.firstName,
|
|
1324
|
+
lastName: options.lastName,
|
|
1325
|
+
email: options.email,
|
|
1326
|
+
phone: options.phone,
|
|
1327
|
+
linkedIn: options.linkedin,
|
|
1328
|
+
telegram: options.telegram,
|
|
1329
|
+
twitter: options.twitter,
|
|
1330
|
+
github: options.github,
|
|
1331
|
+
bluesky: options.bluesky,
|
|
1332
|
+
about: options.about,
|
|
1333
|
+
profileType: options.profileType,
|
|
1334
|
+
skills: options.skills?.split(",").map((s) => s.trim()),
|
|
1335
|
+
tags: options.tags?.split(",").map((t) => t.trim()),
|
|
1336
|
+
organizationId: options.organization
|
|
1337
|
+
});
|
|
1338
|
+
if (useJson) {
|
|
1339
|
+
outputContactJson(contact);
|
|
1340
|
+
} else {
|
|
1341
|
+
console.log("\n\u2713 Contact created successfully");
|
|
1342
|
+
outputContactPretty(contact);
|
|
1343
|
+
}
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
handleError(error, useJson);
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
contacts.command("update").description("Update an existing contact").requiredOption("--id <id>", "Contact ID").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--email <email>", 'Email (use "null" to clear)').option("--phone <phone>", 'Phone (use "null" to clear)').option("--linkedin <url>", 'LinkedIn URL (use "null" to clear)').option("--telegram <handle>", 'Telegram handle (use "null" to clear)').option("--twitter <handle>", 'Twitter/X handle (use "null" to clear)').option("--github <handle>", 'GitHub username (use "null" to clear)').option("--bluesky <handle>", 'Bluesky handle (use "null" to clear)').option("--about <text>", "About / bio").option("--profile-type <type>", "Profile type").option("--skills <skills>", "Skills (comma-separated)").option("--tags <tags>", "Tags (comma-separated)").option("--organization <id>", 'Organization ID (use "null" to clear)').action(async (options, cmd) => {
|
|
1349
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1350
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1351
|
+
const nullable = (v) => v === "null" ? null : v;
|
|
1352
|
+
try {
|
|
1353
|
+
const client = getClient();
|
|
1354
|
+
const contact = await client.contacts.update({
|
|
1355
|
+
id: options.id,
|
|
1356
|
+
firstName: options.firstName,
|
|
1357
|
+
lastName: options.lastName,
|
|
1358
|
+
email: nullable(options.email),
|
|
1359
|
+
phone: nullable(options.phone),
|
|
1360
|
+
linkedIn: nullable(options.linkedin),
|
|
1361
|
+
telegram: nullable(options.telegram),
|
|
1362
|
+
twitter: nullable(options.twitter),
|
|
1363
|
+
github: nullable(options.github),
|
|
1364
|
+
bluesky: nullable(options.bluesky),
|
|
1365
|
+
about: options.about,
|
|
1366
|
+
profileType: options.profileType,
|
|
1367
|
+
skills: options.skills?.split(",").map((s) => s.trim()),
|
|
1368
|
+
tags: options.tags?.split(",").map((t) => t.trim()),
|
|
1369
|
+
organizationId: nullable(options.organization)
|
|
1370
|
+
});
|
|
1371
|
+
if (useJson) {
|
|
1372
|
+
outputContactJson(contact);
|
|
1373
|
+
} else {
|
|
1374
|
+
console.log("\n\u2713 Contact updated successfully");
|
|
1375
|
+
outputContactPretty(contact);
|
|
1376
|
+
}
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
handleError(error, useJson);
|
|
1379
|
+
}
|
|
1380
|
+
});
|
|
1381
|
+
contacts.command("delete").description("Delete a contact").argument("<id>", "Contact ID").action(async (id, _options, cmd) => {
|
|
1382
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1383
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1384
|
+
try {
|
|
1385
|
+
const client = getClient();
|
|
1386
|
+
await client.contacts.delete(id);
|
|
1387
|
+
if (useJson) {
|
|
1388
|
+
console.log(JSON.stringify({ deleted: true, id }, null, 2));
|
|
1389
|
+
} else {
|
|
1390
|
+
console.log(`
|
|
1391
|
+
\u2713 Contact ${id} deleted`);
|
|
1392
|
+
}
|
|
1393
|
+
} catch (error) {
|
|
1394
|
+
handleError(error, useJson);
|
|
1395
|
+
}
|
|
1396
|
+
});
|
|
1397
|
+
contacts.command("add-interaction").description("Add an interaction to a contact").requiredOption("--contact <id>", "Contact ID").requiredOption("--type <type>", `Interaction type (${VALID_INTERACTION_TYPES.join(", ")})`).requiredOption("--direction <dir>", `Direction (${VALID_INTERACTION_DIRECTIONS.join(", ")})`).option("--subject <text>", "Subject line").option("--notes <text>", "Notes").action(async (options, cmd) => {
|
|
1398
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1399
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1400
|
+
try {
|
|
1401
|
+
if (!VALID_INTERACTION_TYPES.includes(options.type)) {
|
|
1402
|
+
throw new Error(`Invalid interaction type "${options.type}". Valid: ${VALID_INTERACTION_TYPES.join(", ")}`);
|
|
1403
|
+
}
|
|
1404
|
+
if (!VALID_INTERACTION_DIRECTIONS.includes(options.direction)) {
|
|
1405
|
+
throw new Error(`Invalid direction "${options.direction}". Valid: ${VALID_INTERACTION_DIRECTIONS.join(", ")}`);
|
|
1406
|
+
}
|
|
1407
|
+
const client = getClient();
|
|
1408
|
+
const interaction = await client.contacts.addInteraction({
|
|
1409
|
+
contactId: options.contact,
|
|
1410
|
+
type: options.type,
|
|
1411
|
+
direction: options.direction,
|
|
1412
|
+
subject: options.subject,
|
|
1413
|
+
notes: options.notes
|
|
1414
|
+
});
|
|
1415
|
+
if (useJson) {
|
|
1416
|
+
outputInteractionJson(interaction);
|
|
1417
|
+
} else {
|
|
1418
|
+
console.log("\n\u2713 Interaction added");
|
|
1419
|
+
outputInteractionPretty(interaction);
|
|
1420
|
+
}
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
handleError(error, useJson);
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
return contacts;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/commands/deals.ts
|
|
1429
|
+
import { Command as Command4 } from "commander";
|
|
1430
|
+
function createDealsCommand() {
|
|
1431
|
+
const deals = new Command4("deals").description("Manage pipeline deals");
|
|
1432
|
+
deals.command("pipeline").description("Get pipeline overview with stages").requiredOption("--workspace <id>", "Workspace ID").action(async (options, cmd) => {
|
|
1433
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1434
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1435
|
+
try {
|
|
1436
|
+
const client = getClient();
|
|
1437
|
+
const pipeline = await client.pipelines.get(options.workspace);
|
|
1438
|
+
if (!pipeline) {
|
|
1439
|
+
if (useJson) {
|
|
1440
|
+
console.log(JSON.stringify({ pipeline: null }, null, 2));
|
|
1441
|
+
} else {
|
|
1442
|
+
console.log("No pipeline found for this workspace. One will be created when you add your first deal.");
|
|
1443
|
+
}
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
if (useJson) {
|
|
1447
|
+
outputPipelineJson(pipeline);
|
|
1448
|
+
} else {
|
|
1449
|
+
outputPipelinePretty(pipeline);
|
|
1450
|
+
}
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
handleError(error, useJson);
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1455
|
+
deals.command("stages").description("List pipeline stages").requiredOption("--workspace <id>", "Workspace ID").action(async (options, cmd) => {
|
|
1456
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1457
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1458
|
+
try {
|
|
1459
|
+
const client = getClient();
|
|
1460
|
+
const stages = await client.pipelines.getStages(options.workspace);
|
|
1461
|
+
if (useJson) {
|
|
1462
|
+
outputStagesJson(stages);
|
|
1463
|
+
} else {
|
|
1464
|
+
outputStagesPretty(stages);
|
|
1465
|
+
}
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
handleError(error, useJson);
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
deals.command("list").description("List all deals").requiredOption("--workspace <id>", "Workspace ID").action(async (options, cmd) => {
|
|
1471
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1472
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1473
|
+
try {
|
|
1474
|
+
const client = getClient();
|
|
1475
|
+
const dealsList = await client.pipelines.listDeals(options.workspace);
|
|
1476
|
+
if (useJson) {
|
|
1477
|
+
outputDealsJson(dealsList);
|
|
1478
|
+
} else {
|
|
1479
|
+
outputDealsPretty(dealsList);
|
|
1480
|
+
}
|
|
1481
|
+
} catch (error) {
|
|
1482
|
+
handleError(error, useJson);
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
deals.command("get").description("Get a deal by ID").argument("<id>", "Deal ID").action(async (id, _options, cmd) => {
|
|
1486
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1487
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1488
|
+
try {
|
|
1489
|
+
const client = getClient();
|
|
1490
|
+
const deal = await client.pipelines.getDeal(id);
|
|
1491
|
+
if (useJson) {
|
|
1492
|
+
outputDealJson(deal);
|
|
1493
|
+
} else {
|
|
1494
|
+
outputDealPretty(deal);
|
|
1495
|
+
}
|
|
1496
|
+
} catch (error) {
|
|
1497
|
+
handleError(error, useJson);
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
deals.command("create").description("Create a new deal").requiredOption("--workspace <id>", "Workspace ID").requiredOption("--stage <id>", "Pipeline stage ID").requiredOption("--title <title>", "Deal title").option("--description <text>", "Deal description").option("--value <amount>", "Deal value", parseFloat).option("--currency <code>", "Currency code (default: USD)").option("--probability <pct>", "Win probability (0-100)", parseInt).option("--close-date <date>", "Expected close date (YYYY-MM-DD)").option("--contact <id>", "Contact ID").option("--organization <id>", "Organization ID").option("--assigned-to <id>", "Assigned user ID").action(async (options, cmd) => {
|
|
1501
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1502
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1503
|
+
try {
|
|
1504
|
+
let expectedCloseDate;
|
|
1505
|
+
if (options.closeDate) {
|
|
1506
|
+
expectedCloseDate = new Date(options.closeDate);
|
|
1507
|
+
if (isNaN(expectedCloseDate.getTime())) {
|
|
1508
|
+
throw new Error("Invalid close date format. Use YYYY-MM-DD");
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
const client = getClient();
|
|
1512
|
+
const deal = await client.pipelines.createDeal({
|
|
1513
|
+
workspaceId: options.workspace,
|
|
1514
|
+
stageId: options.stage,
|
|
1515
|
+
title: options.title,
|
|
1516
|
+
description: options.description,
|
|
1517
|
+
value: options.value,
|
|
1518
|
+
currency: options.currency,
|
|
1519
|
+
probability: options.probability,
|
|
1520
|
+
expectedCloseDate,
|
|
1521
|
+
contactId: options.contact,
|
|
1522
|
+
organizationId: options.organization,
|
|
1523
|
+
assignedToId: options.assignedTo
|
|
1524
|
+
});
|
|
1525
|
+
if (useJson) {
|
|
1526
|
+
outputDealJson(deal);
|
|
1527
|
+
} else {
|
|
1528
|
+
console.log("\n\u2713 Deal created successfully");
|
|
1529
|
+
outputDealPretty(deal);
|
|
1530
|
+
}
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
handleError(error, useJson);
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
deals.command("update").description("Update an existing deal").requiredOption("--id <id>", "Deal ID").option("--title <title>", "Deal title").option("--description <text>", 'Description (use "null" to clear)').option("--value <amount>", 'Deal value (use "null" to clear)').option("--currency <code>", "Currency code").option("--probability <pct>", 'Win probability (use "null" to clear)').option("--close-date <date>", 'Expected close date (YYYY-MM-DD or "null" to clear)').option("--contact <id>", 'Contact ID (use "null" to clear)').option("--organization <id>", 'Organization ID (use "null" to clear)').option("--assigned-to <id>", 'Assigned user ID (use "null" to clear)').action(async (options, cmd) => {
|
|
1536
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1537
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1538
|
+
const nullable = (v, parse) => {
|
|
1539
|
+
if (v === void 0) return void 0;
|
|
1540
|
+
if (v === "null") return null;
|
|
1541
|
+
return parse ? parse(v) : v;
|
|
1542
|
+
};
|
|
1543
|
+
try {
|
|
1544
|
+
let expectedCloseDate;
|
|
1545
|
+
if (options.closeDate === "null") {
|
|
1546
|
+
expectedCloseDate = null;
|
|
1547
|
+
} else if (options.closeDate) {
|
|
1548
|
+
expectedCloseDate = new Date(options.closeDate);
|
|
1549
|
+
if (isNaN(expectedCloseDate.getTime())) {
|
|
1550
|
+
throw new Error("Invalid close date format. Use YYYY-MM-DD");
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
const client = getClient();
|
|
1554
|
+
const deal = await client.pipelines.updateDeal({
|
|
1555
|
+
id: options.id,
|
|
1556
|
+
title: options.title,
|
|
1557
|
+
description: nullable(options.description),
|
|
1558
|
+
value: nullable(options.value, parseFloat),
|
|
1559
|
+
currency: options.currency,
|
|
1560
|
+
probability: nullable(options.probability, parseInt),
|
|
1561
|
+
expectedCloseDate,
|
|
1562
|
+
contactId: nullable(options.contact),
|
|
1563
|
+
organizationId: nullable(options.organization),
|
|
1564
|
+
assignedToId: nullable(options.assignedTo)
|
|
1565
|
+
});
|
|
1566
|
+
if (useJson) {
|
|
1567
|
+
outputDealJson(deal);
|
|
1568
|
+
} else {
|
|
1569
|
+
console.log("\n\u2713 Deal updated successfully");
|
|
1570
|
+
outputDealPretty(deal);
|
|
1571
|
+
}
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
handleError(error, useJson);
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
deals.command("move").description("Move a deal to a different stage").requiredOption("--id <id>", "Deal ID").requiredOption("--stage <id>", "Target stage ID").option("--order <n>", "Position within stage", parseInt, 0).action(async (options, cmd) => {
|
|
1577
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1578
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1579
|
+
try {
|
|
1580
|
+
const client = getClient();
|
|
1581
|
+
const deal = await client.pipelines.moveDeal({
|
|
1582
|
+
id: options.id,
|
|
1583
|
+
stageId: options.stage,
|
|
1584
|
+
stageOrder: options.order
|
|
1585
|
+
});
|
|
1586
|
+
if (useJson) {
|
|
1587
|
+
outputDealJson(deal);
|
|
1588
|
+
} else {
|
|
1589
|
+
console.log("\n\u2713 Deal moved successfully");
|
|
1590
|
+
outputDealPretty(deal);
|
|
1591
|
+
}
|
|
1592
|
+
} catch (error) {
|
|
1593
|
+
handleError(error, useJson);
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
deals.command("delete").description("Delete a deal").argument("<id>", "Deal ID").action(async (id, _options, cmd) => {
|
|
1597
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1598
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1599
|
+
try {
|
|
1600
|
+
const client = getClient();
|
|
1601
|
+
await client.pipelines.deleteDeal(id);
|
|
1602
|
+
if (useJson) {
|
|
1603
|
+
console.log(JSON.stringify({ deleted: true, id }, null, 2));
|
|
1604
|
+
} else {
|
|
1605
|
+
console.log(`
|
|
1606
|
+
\u2713 Deal ${id} deleted`);
|
|
1607
|
+
}
|
|
1608
|
+
} catch (error) {
|
|
1609
|
+
handleError(error, useJson);
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
1612
|
+
return deals;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// src/commands/epics.ts
|
|
1616
|
+
import { Command as Command5 } from "commander";
|
|
1617
|
+
|
|
1618
|
+
// src/utils/resolve.ts
|
|
1619
|
+
async function resolveWorkspaceId(client, slugOrId) {
|
|
1620
|
+
const config = getConfig();
|
|
1621
|
+
const candidate = slugOrId ?? config.defaultWorkspaceSlug ?? config.defaultWorkspaceId;
|
|
1622
|
+
if (!candidate) {
|
|
1623
|
+
throw new Error(
|
|
1624
|
+
"No workspace specified and no default workspace set. Pass --workspace <slug> or run `exponential workspaces set-default <slug>`."
|
|
1625
|
+
);
|
|
1626
|
+
}
|
|
1627
|
+
const workspaces = await client.workspaces.list();
|
|
1628
|
+
const byId = workspaces.find((w) => w.id === candidate);
|
|
1629
|
+
if (byId) return byId.id;
|
|
1630
|
+
const bySlug = workspaces.find((w) => w.slug === candidate);
|
|
1631
|
+
if (bySlug) return bySlug.id;
|
|
1632
|
+
const known = workspaces.map((w) => w.slug).join(", ");
|
|
1633
|
+
throw new Error(
|
|
1634
|
+
`Workspace "${candidate}" not found. Available: ${known || "(none)"}`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
async function resolveProductId(client, workspaceId, slugOrId) {
|
|
1638
|
+
const product = await client.products.resolve(workspaceId, slugOrId);
|
|
1639
|
+
return product.id;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// src/commands/epics.ts
|
|
1643
|
+
var EPIC_STATUSES = ["OPEN", "IN_PROGRESS", "DONE", "CANCELLED"];
|
|
1644
|
+
var EPIC_PRIORITIES = ["HIGH", "MEDIUM", "LOW", "NONE"];
|
|
1645
|
+
function validateEpicStatus(value) {
|
|
1646
|
+
if (!value) return void 0;
|
|
1647
|
+
if (!EPIC_STATUSES.includes(value)) {
|
|
1648
|
+
throw new Error(`Invalid epic status "${value}". Valid: ${EPIC_STATUSES.join(", ")}`);
|
|
1649
|
+
}
|
|
1650
|
+
return value;
|
|
1651
|
+
}
|
|
1652
|
+
function validateEpicPriority(value) {
|
|
1653
|
+
if (!value) return void 0;
|
|
1654
|
+
if (!EPIC_PRIORITIES.includes(value)) {
|
|
1655
|
+
throw new Error(
|
|
1656
|
+
`Invalid epic priority "${value}". Valid: ${EPIC_PRIORITIES.join(", ")}`
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
return value;
|
|
1660
|
+
}
|
|
1661
|
+
function parseDate(value) {
|
|
1662
|
+
if (!value) return void 0;
|
|
1663
|
+
const d = new Date(value);
|
|
1664
|
+
if (isNaN(d.getTime())) {
|
|
1665
|
+
throw new Error(`Invalid date "${value}". Use YYYY-MM-DD`);
|
|
1666
|
+
}
|
|
1667
|
+
return d;
|
|
1668
|
+
}
|
|
1669
|
+
function createEpicsCommand() {
|
|
1670
|
+
const epics = new Command5("epics").description(
|
|
1671
|
+
"Manage epics. Epics are workspace-scoped and group actions and tickets across products."
|
|
1672
|
+
);
|
|
1673
|
+
epics.command("list").description("List epics in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID").option("--status <status>", `Filter by status: ${EPIC_STATUSES.join(", ")}`).action(
|
|
1674
|
+
async (options, cmd) => {
|
|
1675
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1676
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1677
|
+
try {
|
|
1678
|
+
const status = validateEpicStatus(options.status);
|
|
1679
|
+
const client = getClient();
|
|
1680
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
1681
|
+
const list = await client.epics.list({ workspaceId, status });
|
|
1682
|
+
if (useJson) outputEpicsJson(list);
|
|
1683
|
+
else outputEpicsPretty(list);
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
handleError(error, useJson);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
);
|
|
1689
|
+
epics.command("get <id>").description("Get an epic by CUID").action(async (id, _options, cmd) => {
|
|
1690
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1691
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1692
|
+
try {
|
|
1693
|
+
const client = getClient();
|
|
1694
|
+
const epic = await client.epics.get(id);
|
|
1695
|
+
if (useJson) outputEpicJson(epic);
|
|
1696
|
+
else outputEpicPretty(epic);
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
handleError(error, useJson);
|
|
1699
|
+
}
|
|
1700
|
+
});
|
|
1701
|
+
epics.command("create").description("Create a new epic in a workspace").requiredOption("-n, --name <name>", "Epic name").option("-d, --description <text>", "Description").option("--priority <priority>", `Priority: ${EPIC_PRIORITIES.join(", ")}`).option("--start <date>", "Start date (YYYY-MM-DD)").option("--target <date>", "Target date (YYYY-MM-DD)").option("--workspace <slug|id>", "Workspace slug or CUID").action(
|
|
1702
|
+
async (options, cmd) => {
|
|
1703
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1704
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1705
|
+
try {
|
|
1706
|
+
const priority = validateEpicPriority(options.priority);
|
|
1707
|
+
const startDate = parseDate(options.start);
|
|
1708
|
+
const targetDate = parseDate(options.target);
|
|
1709
|
+
const client = getClient();
|
|
1710
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
1711
|
+
const epic = await client.epics.create({
|
|
1712
|
+
workspaceId,
|
|
1713
|
+
name: options.name,
|
|
1714
|
+
description: options.description,
|
|
1715
|
+
priority,
|
|
1716
|
+
startDate,
|
|
1717
|
+
targetDate
|
|
1718
|
+
});
|
|
1719
|
+
if (useJson) outputEpicJson(epic);
|
|
1720
|
+
else {
|
|
1721
|
+
console.log("\n\u2713 Epic created");
|
|
1722
|
+
outputEpicPretty(epic);
|
|
1723
|
+
}
|
|
1724
|
+
} catch (error) {
|
|
1725
|
+
handleError(error, useJson);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
);
|
|
1729
|
+
epics.command("update").description("Update an existing epic").requiredOption("--id <id>", "Epic CUID").option("-n, --name <name>", "New name").option("-d, --description <text>", 'New description (or "null" to clear)').option("--status <status>", `Status: ${EPIC_STATUSES.join(", ")}`).option("--priority <priority>", `Priority: ${EPIC_PRIORITIES.join(", ")}`).option("--start <date>", 'Start date (YYYY-MM-DD or "null")').option("--target <date>", 'Target date (YYYY-MM-DD or "null")').action(
|
|
1730
|
+
async (options, cmd) => {
|
|
1731
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1732
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1733
|
+
try {
|
|
1734
|
+
const parseNullableDate = (v) => {
|
|
1735
|
+
if (v === void 0) return void 0;
|
|
1736
|
+
if (v === "null") return null;
|
|
1737
|
+
const d = new Date(v);
|
|
1738
|
+
if (isNaN(d.getTime())) {
|
|
1739
|
+
throw new Error(`Invalid date "${v}". Use YYYY-MM-DD`);
|
|
1740
|
+
}
|
|
1741
|
+
return d;
|
|
1742
|
+
};
|
|
1743
|
+
const description = options.description === "null" ? null : options.description;
|
|
1744
|
+
const status = validateEpicStatus(options.status);
|
|
1745
|
+
const priority = validateEpicPriority(options.priority);
|
|
1746
|
+
const client = getClient();
|
|
1747
|
+
const epic = await client.epics.update({
|
|
1748
|
+
id: options.id,
|
|
1749
|
+
name: options.name,
|
|
1750
|
+
description,
|
|
1751
|
+
status,
|
|
1752
|
+
priority,
|
|
1753
|
+
startDate: parseNullableDate(options.start),
|
|
1754
|
+
targetDate: parseNullableDate(options.target)
|
|
1755
|
+
});
|
|
1756
|
+
if (useJson) outputEpicJson(epic);
|
|
1757
|
+
else {
|
|
1758
|
+
console.log("\n\u2713 Epic updated");
|
|
1759
|
+
outputEpicPretty(epic);
|
|
1760
|
+
}
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
handleError(error, useJson);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
);
|
|
1766
|
+
return epics;
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/commands/features.ts
|
|
1770
|
+
import { Command as Command6 } from "commander";
|
|
1771
|
+
var FEATURE_STATUSES = [
|
|
1772
|
+
"IDEA",
|
|
1773
|
+
"DEFINED",
|
|
1774
|
+
"IN_PROGRESS",
|
|
1775
|
+
"SHIPPED",
|
|
1776
|
+
"ARCHIVED"
|
|
1777
|
+
];
|
|
1778
|
+
function validateFeatureStatus(value) {
|
|
1779
|
+
if (!value) return void 0;
|
|
1780
|
+
if (!FEATURE_STATUSES.includes(value)) {
|
|
1781
|
+
throw new Error(
|
|
1782
|
+
`Invalid status "${value}". Valid: ${FEATURE_STATUSES.join(", ")}`
|
|
1783
|
+
);
|
|
1784
|
+
}
|
|
1785
|
+
return value;
|
|
1786
|
+
}
|
|
1787
|
+
function createFeaturesCommand() {
|
|
1788
|
+
const features = new Command6("features").description(
|
|
1789
|
+
"Manage features. A feature lives under a product and groups user stories, scopes, and tickets."
|
|
1790
|
+
);
|
|
1791
|
+
features.command("list").description("List features in a product").requiredOption("--product <slug|id>", "Product slug or CUID").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").option("--status <status>", `Filter by status: ${FEATURE_STATUSES.join(", ")}`).action(
|
|
1792
|
+
async (options, cmd) => {
|
|
1793
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1794
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1795
|
+
try {
|
|
1796
|
+
const status = validateFeatureStatus(options.status);
|
|
1797
|
+
const client = getClient();
|
|
1798
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
1799
|
+
const productId = await resolveProductId(
|
|
1800
|
+
client,
|
|
1801
|
+
workspaceId,
|
|
1802
|
+
options.product
|
|
1803
|
+
);
|
|
1804
|
+
const list = await client.features.list({ productId, status });
|
|
1805
|
+
if (useJson) outputFeaturesJson(list);
|
|
1806
|
+
else outputFeaturesPretty(list);
|
|
1807
|
+
} catch (error) {
|
|
1808
|
+
handleError(error, useJson);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
);
|
|
1812
|
+
features.command("get <id>").description("Get a feature by CUID").action(async (id, _options, cmd) => {
|
|
1813
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1814
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1815
|
+
try {
|
|
1816
|
+
const client = getClient();
|
|
1817
|
+
const feature = await client.features.get(id);
|
|
1818
|
+
if (useJson) outputFeatureJson(feature);
|
|
1819
|
+
else outputFeaturePretty(feature);
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
handleError(error, useJson);
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
features.command("create").description("Create a new feature in a product").requiredOption("--product <slug|id>", "Product slug or CUID").requiredOption("-n, --name <name>", "Feature name").option("-d, --description <text>", "Description (markdown supported)").option("--vision <text>", "Vision / target outcome").option("--status <status>", `Status: ${FEATURE_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4 (lower = higher)", parseInt).option("--effort <n>", "Effort estimate", parseFloat).option("--workspace <slug|id>", "Workspace (required when --product is a slug)").action(
|
|
1825
|
+
async (options, cmd) => {
|
|
1826
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1827
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1828
|
+
try {
|
|
1829
|
+
const status = validateFeatureStatus(options.status);
|
|
1830
|
+
const client = getClient();
|
|
1831
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
1832
|
+
const productId = await resolveProductId(
|
|
1833
|
+
client,
|
|
1834
|
+
workspaceId,
|
|
1835
|
+
options.product
|
|
1836
|
+
);
|
|
1837
|
+
const feature = await client.features.create({
|
|
1838
|
+
productId,
|
|
1839
|
+
name: options.name,
|
|
1840
|
+
description: options.description,
|
|
1841
|
+
vision: options.vision,
|
|
1842
|
+
status,
|
|
1843
|
+
priority: options.priority,
|
|
1844
|
+
effort: options.effort
|
|
1845
|
+
});
|
|
1846
|
+
if (useJson) outputFeatureJson(feature);
|
|
1847
|
+
else {
|
|
1848
|
+
console.log("\n\u2713 Feature created");
|
|
1849
|
+
outputFeaturePretty(feature);
|
|
1850
|
+
}
|
|
1851
|
+
} catch (error) {
|
|
1852
|
+
handleError(error, useJson);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
);
|
|
1856
|
+
features.command("update").description("Update an existing feature").requiredOption("--id <id>", "Feature CUID").option("-n, --name <name>", "New name").option("-d, --description <text>", "New description").option("--vision <text>", "New vision text").option("--status <status>", `Status: ${FEATURE_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4", parseInt).option("--effort <n>", "Effort estimate", parseFloat).action(
|
|
1857
|
+
async (options, cmd) => {
|
|
1858
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1859
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1860
|
+
try {
|
|
1861
|
+
const status = validateFeatureStatus(options.status);
|
|
1862
|
+
const client = getClient();
|
|
1863
|
+
const feature = await client.features.update({
|
|
1864
|
+
id: options.id,
|
|
1865
|
+
name: options.name,
|
|
1866
|
+
description: options.description,
|
|
1867
|
+
vision: options.vision,
|
|
1868
|
+
status,
|
|
1869
|
+
priority: options.priority,
|
|
1870
|
+
effort: options.effort
|
|
1871
|
+
});
|
|
1872
|
+
if (useJson) outputFeatureJson(feature);
|
|
1873
|
+
else {
|
|
1874
|
+
console.log("\n\u2713 Feature updated");
|
|
1875
|
+
outputFeaturePretty(feature);
|
|
1876
|
+
}
|
|
1877
|
+
} catch (error) {
|
|
1878
|
+
handleError(error, useJson);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
);
|
|
1882
|
+
return features;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// src/commands/products.ts
|
|
1886
|
+
import { Command as Command7 } from "commander";
|
|
1887
|
+
function createProductsCommand() {
|
|
1888
|
+
const products = new Command7("products").description(
|
|
1889
|
+
"Manage products. A workspace contains products; a product holds features and tickets."
|
|
1890
|
+
);
|
|
1891
|
+
products.command("list").description("List products in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID (defaults to configured workspace)").action(async (options, cmd) => {
|
|
1892
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1893
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1894
|
+
try {
|
|
1895
|
+
const client = getClient();
|
|
1896
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
1897
|
+
const list = await client.products.list(workspaceId);
|
|
1898
|
+
if (useJson) outputProductsJson(list);
|
|
1899
|
+
else outputProductsPretty(list);
|
|
1900
|
+
} catch (error) {
|
|
1901
|
+
handleError(error, useJson);
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
products.command("get <slugOrId>").description("Get a product by slug or CUID").option("--workspace <slug|id>", "Workspace (required when using a slug)").action(async (slugOrId, options, cmd) => {
|
|
1905
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1906
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1907
|
+
try {
|
|
1908
|
+
const client = getClient();
|
|
1909
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
1910
|
+
const product = await client.products.resolve(workspaceId, slugOrId);
|
|
1911
|
+
if (useJson) outputProductJson(product);
|
|
1912
|
+
else outputProductPretty(product);
|
|
1913
|
+
} catch (error) {
|
|
1914
|
+
handleError(error, useJson);
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
products.command("create").description("Create a new product").requiredOption("-n, --name <name>", "Product name").requiredOption("--slug <slug>", "Kebab-case slug (matches /^[a-z0-9-]+$/)").option("-d, --description <text>", "Description").option("--icon <icon>", "Icon shortcode").option("--color <color>", "Color").option("--workspace <slug|id>", "Workspace slug or CUID").action(
|
|
1918
|
+
async (options, cmd) => {
|
|
1919
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1920
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1921
|
+
try {
|
|
1922
|
+
if (!/^[a-z0-9-]+$/.test(options.slug)) {
|
|
1923
|
+
throw new Error(
|
|
1924
|
+
`Invalid slug "${options.slug}". Slug must be kebab-case (a-z, 0-9, -).`
|
|
1925
|
+
);
|
|
1926
|
+
}
|
|
1927
|
+
const client = getClient();
|
|
1928
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
1929
|
+
const product = await client.products.create({
|
|
1930
|
+
workspaceId,
|
|
1931
|
+
name: options.name,
|
|
1932
|
+
slug: options.slug,
|
|
1933
|
+
description: options.description,
|
|
1934
|
+
icon: options.icon,
|
|
1935
|
+
color: options.color
|
|
1936
|
+
});
|
|
1937
|
+
if (useJson) outputProductJson(product);
|
|
1938
|
+
else {
|
|
1939
|
+
console.log("\n\u2713 Product created");
|
|
1940
|
+
outputProductPretty(product);
|
|
1941
|
+
}
|
|
1942
|
+
} catch (error) {
|
|
1943
|
+
handleError(error, useJson);
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
);
|
|
1947
|
+
return products;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// src/commands/projects.ts
|
|
1951
|
+
import { Command as Command8 } from "commander";
|
|
1952
|
+
function createProjectsCommand() {
|
|
1953
|
+
const projects = new Command8("projects").description("Manage projects");
|
|
1954
|
+
projects.command("list").description("List all projects").option("--workspace <id>", "Filter by workspace ID").option("--include-actions", "Include actions in output").action(async (options, cmd) => {
|
|
1955
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1956
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
1957
|
+
try {
|
|
1958
|
+
const client = getClient();
|
|
1959
|
+
const projects2 = await client.projects.list({
|
|
1960
|
+
workspaceId: options.workspace,
|
|
1961
|
+
includeActions: options.includeActions
|
|
1962
|
+
});
|
|
1963
|
+
if (useJson) {
|
|
1964
|
+
outputProjectsJson(projects2);
|
|
1965
|
+
} else {
|
|
1966
|
+
outputProjectsPretty(projects2);
|
|
1967
|
+
}
|
|
1968
|
+
} catch (error) {
|
|
1969
|
+
handleError(error, useJson);
|
|
1970
|
+
}
|
|
1971
|
+
});
|
|
1972
|
+
return projects;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// src/commands/tickets.ts
|
|
1976
|
+
import { Command as Command9 } from "commander";
|
|
1977
|
+
var TICKET_STATUSES = [
|
|
1978
|
+
"BACKLOG",
|
|
1979
|
+
"NEEDS_REFINEMENT",
|
|
1980
|
+
"READY_TO_PLAN",
|
|
1981
|
+
"COMMITTED",
|
|
1982
|
+
"IN_PROGRESS",
|
|
1983
|
+
"BLOCKED",
|
|
1984
|
+
"QA",
|
|
1985
|
+
"DONE",
|
|
1986
|
+
"DEPLOYED",
|
|
1987
|
+
"ARCHIVED"
|
|
1988
|
+
];
|
|
1989
|
+
var TICKET_TYPES = [
|
|
1990
|
+
"BUG",
|
|
1991
|
+
"FEATURE",
|
|
1992
|
+
"CHORE",
|
|
1993
|
+
"IMPROVEMENT",
|
|
1994
|
+
"SPIKE",
|
|
1995
|
+
"RESEARCH"
|
|
1996
|
+
];
|
|
1997
|
+
function validateTicketStatus(value) {
|
|
1998
|
+
if (!value) return void 0;
|
|
1999
|
+
if (!TICKET_STATUSES.includes(value)) {
|
|
2000
|
+
throw new Error(
|
|
2001
|
+
`Invalid ticket status "${value}". Valid: ${TICKET_STATUSES.join(", ")}`
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
return value;
|
|
2005
|
+
}
|
|
2006
|
+
function validateTicketType(value) {
|
|
2007
|
+
if (!value) return void 0;
|
|
2008
|
+
if (!TICKET_TYPES.includes(value)) {
|
|
2009
|
+
throw new Error(
|
|
2010
|
+
`Invalid ticket type "${value}". Valid: ${TICKET_TYPES.join(", ")}`
|
|
2011
|
+
);
|
|
2012
|
+
}
|
|
2013
|
+
return value;
|
|
2014
|
+
}
|
|
2015
|
+
function createTicketsCommand() {
|
|
2016
|
+
const tickets = new Command9("tickets").description(
|
|
2017
|
+
"Manage product backlog tickets. Tickets live under a product, link to features/epics, depend on each other, and can be implemented via actions."
|
|
2018
|
+
);
|
|
2019
|
+
tickets.command("list").description("List tickets in a product").requiredOption("--product <slug|id>", "Product slug or CUID").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").option("--status <status>", `Filter by status: ${TICKET_STATUSES.join(", ")}`).option("--type <type>", `Filter by type: ${TICKET_TYPES.join(", ")}`).option("--feature <id>", "Filter by feature CUID").option("--epic <id>", "Filter by epic CUID").option("--assignee <id>", "Filter by assignee user ID").action(
|
|
2020
|
+
async (options, cmd) => {
|
|
2021
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2022
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2023
|
+
try {
|
|
2024
|
+
const status = validateTicketStatus(options.status);
|
|
2025
|
+
const type = validateTicketType(options.type);
|
|
2026
|
+
const client = getClient();
|
|
2027
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
2028
|
+
const productId = await resolveProductId(
|
|
2029
|
+
client,
|
|
2030
|
+
workspaceId,
|
|
2031
|
+
options.product
|
|
2032
|
+
);
|
|
2033
|
+
const list = await client.tickets.list({
|
|
2034
|
+
productId,
|
|
2035
|
+
status,
|
|
2036
|
+
type,
|
|
2037
|
+
featureId: options.feature,
|
|
2038
|
+
epicId: options.epic,
|
|
2039
|
+
assigneeId: options.assignee
|
|
2040
|
+
});
|
|
2041
|
+
if (useJson) outputTicketsJson(list);
|
|
2042
|
+
else outputTicketsPretty(list);
|
|
2043
|
+
} catch (error) {
|
|
2044
|
+
handleError(error, useJson);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
);
|
|
2048
|
+
tickets.command("get <id>").description("Show a ticket with dependencies, actions, and comments").action(async (id, _options, cmd) => {
|
|
2049
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2050
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2051
|
+
try {
|
|
2052
|
+
const client = getClient();
|
|
2053
|
+
const ticket = await client.tickets.get(id);
|
|
2054
|
+
if (useJson) outputTicketJson(ticket);
|
|
2055
|
+
else outputTicketPretty(ticket);
|
|
2056
|
+
} catch (error) {
|
|
2057
|
+
handleError(error, useJson);
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
tickets.command("show <id>").description("Alias for `tickets get`").action(async (id, _options, cmd) => {
|
|
2061
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2062
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2063
|
+
try {
|
|
2064
|
+
const client = getClient();
|
|
2065
|
+
const ticket = await client.tickets.get(id);
|
|
2066
|
+
if (useJson) outputTicketJson(ticket);
|
|
2067
|
+
else outputTicketPretty(ticket);
|
|
2068
|
+
} catch (error) {
|
|
2069
|
+
handleError(error, useJson);
|
|
2070
|
+
}
|
|
2071
|
+
});
|
|
2072
|
+
tickets.command("create").description("Create a new ticket in a product").requiredOption("--product <slug|id>", "Product slug or CUID").requiredOption("-t, --title <title>", "Ticket title").option("-b, --body <text>", "Body (markdown supported)").option("--type <type>", `Ticket type: ${TICKET_TYPES.join(", ")}`).option("--status <status>", `Initial status: ${TICKET_STATUSES.join(", ")}`).option("--priority <n>", "Priority 0-4 (lower = higher)", parseInt).option("--points <n>", "Story points", parseFloat).option("--feature <id>", "Feature CUID to attach to").option("--epic <id>", "Epic CUID to attach to").option("--assignee <id>", "Assignee user ID").option("--branch <name>", "Branch name").option("--pr <url>", "Pull request URL").option("--workspace <slug|id>", "Workspace (required when --product is a slug)").action(
|
|
2073
|
+
async (options, cmd) => {
|
|
2074
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2075
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2076
|
+
try {
|
|
2077
|
+
const status = validateTicketStatus(options.status);
|
|
2078
|
+
const type = validateTicketType(options.type);
|
|
2079
|
+
const client = getClient();
|
|
2080
|
+
const workspaceId = await resolveWorkspaceId(client, options.workspace);
|
|
2081
|
+
const productId = await resolveProductId(
|
|
2082
|
+
client,
|
|
2083
|
+
workspaceId,
|
|
2084
|
+
options.product
|
|
2085
|
+
);
|
|
2086
|
+
const ticket = await client.tickets.create({
|
|
2087
|
+
productId,
|
|
2088
|
+
title: options.title,
|
|
2089
|
+
body: options.body,
|
|
2090
|
+
type,
|
|
2091
|
+
status,
|
|
2092
|
+
priority: options.priority,
|
|
2093
|
+
points: options.points,
|
|
2094
|
+
featureId: options.feature,
|
|
2095
|
+
epicId: options.epic,
|
|
2096
|
+
assigneeId: options.assignee,
|
|
2097
|
+
branchName: options.branch,
|
|
2098
|
+
prUrl: options.pr
|
|
2099
|
+
});
|
|
2100
|
+
if (useJson) outputTicketJson(ticket);
|
|
2101
|
+
else {
|
|
2102
|
+
console.log("\n\u2713 Ticket created");
|
|
2103
|
+
outputTicketPretty(ticket);
|
|
2104
|
+
}
|
|
2105
|
+
} catch (error) {
|
|
2106
|
+
handleError(error, useJson);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
);
|
|
2110
|
+
tickets.command("update").description("Update an existing ticket").requiredOption("--id <id>", "Ticket CUID").option("-t, --title <title>", "New title").option("-b, --body <text>", "New body").option("--type <type>", `Type: ${TICKET_TYPES.join(", ")}`).option("--status <status>", `Status: ${TICKET_STATUSES.join(", ")}`).option("--priority <n>", 'Priority 0-4 (or "null" to clear)').option("--points <n>", 'Story points (or "null" to clear)').option("--feature <id>", 'Feature CUID (or "null" to detach)').option("--epic <id>", 'Epic CUID (or "null" to detach)').option("--assignee <id>", 'Assignee user ID (or "null" to unassign)').option("--branch <name>", 'Branch name (or "null")').option("--pr <url>", 'PR URL (or "null")').action(
|
|
2111
|
+
async (options, cmd) => {
|
|
2112
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2113
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2114
|
+
try {
|
|
2115
|
+
const parseNullableInt = (v) => {
|
|
2116
|
+
if (v === void 0) return void 0;
|
|
2117
|
+
if (v === "null") return null;
|
|
2118
|
+
const n = parseInt(v, 10);
|
|
2119
|
+
if (isNaN(n)) throw new Error(`Invalid number: ${v}`);
|
|
2120
|
+
return n;
|
|
2121
|
+
};
|
|
2122
|
+
const parseNullableFloat = (v) => {
|
|
2123
|
+
if (v === void 0) return void 0;
|
|
2124
|
+
if (v === "null") return null;
|
|
2125
|
+
const n = parseFloat(v);
|
|
2126
|
+
if (isNaN(n)) throw new Error(`Invalid number: ${v}`);
|
|
2127
|
+
return n;
|
|
2128
|
+
};
|
|
2129
|
+
const parseNullableString = (v) => {
|
|
2130
|
+
if (v === void 0) return void 0;
|
|
2131
|
+
if (v === "null") return null;
|
|
2132
|
+
return v;
|
|
2133
|
+
};
|
|
2134
|
+
const status = validateTicketStatus(options.status);
|
|
2135
|
+
const type = validateTicketType(options.type);
|
|
2136
|
+
const client = getClient();
|
|
2137
|
+
const ticket = await client.tickets.update({
|
|
2138
|
+
id: options.id,
|
|
2139
|
+
title: options.title,
|
|
2140
|
+
body: options.body,
|
|
2141
|
+
type,
|
|
2142
|
+
status,
|
|
2143
|
+
priority: parseNullableInt(options.priority),
|
|
2144
|
+
points: parseNullableFloat(options.points),
|
|
2145
|
+
featureId: parseNullableString(options.feature),
|
|
2146
|
+
epicId: parseNullableString(options.epic),
|
|
2147
|
+
assigneeId: parseNullableString(options.assignee),
|
|
2148
|
+
branchName: parseNullableString(options.branch),
|
|
2149
|
+
prUrl: parseNullableString(options.pr)
|
|
2150
|
+
});
|
|
2151
|
+
if (useJson) outputTicketJson(ticket);
|
|
2152
|
+
else {
|
|
2153
|
+
console.log("\n\u2713 Ticket updated");
|
|
2154
|
+
outputTicketPretty(ticket);
|
|
2155
|
+
}
|
|
2156
|
+
} catch (error) {
|
|
2157
|
+
handleError(error, useJson);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
);
|
|
2161
|
+
tickets.command("delete <id>").description("Delete a ticket").action(async (id, _options, cmd) => {
|
|
2162
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2163
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2164
|
+
try {
|
|
2165
|
+
const client = getClient();
|
|
2166
|
+
await client.tickets.delete(id);
|
|
2167
|
+
if (useJson) {
|
|
2168
|
+
console.log(JSON.stringify({ deleted: true, id }, null, 2));
|
|
2169
|
+
} else {
|
|
2170
|
+
console.log("\n\u2713 Ticket deleted");
|
|
2171
|
+
}
|
|
2172
|
+
} catch (error) {
|
|
2173
|
+
handleError(error, useJson);
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
tickets.command("block <ticket>").description("Mark <ticket> as blocked by another ticket").requiredOption("--by <id>", "Ticket CUID that must complete first").action(
|
|
2177
|
+
async (ticket, options, cmd) => {
|
|
2178
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2179
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2180
|
+
try {
|
|
2181
|
+
const client = getClient();
|
|
2182
|
+
const dep = await client.tickets.addDependency(ticket, options.by);
|
|
2183
|
+
if (useJson) {
|
|
2184
|
+
console.log(JSON.stringify(dep, null, 2));
|
|
2185
|
+
} else {
|
|
2186
|
+
const depId = dep.dependsOn.shortId ?? (dep.dependsOn.number != null ? `#${dep.dependsOn.number}` : dep.dependsOn.id);
|
|
2187
|
+
console.log(`
|
|
2188
|
+
\u2713 Ticket ${ticket} now depends on ${depId} ${dep.dependsOn.title}`);
|
|
2189
|
+
}
|
|
2190
|
+
} catch (error) {
|
|
2191
|
+
handleError(error, useJson);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
);
|
|
2195
|
+
tickets.command("unblock <ticket>").description("Remove a blocking dependency from <ticket>").requiredOption("--by <id>", "The ticket CUID that was blocking").action(
|
|
2196
|
+
async (ticket, options, cmd) => {
|
|
2197
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2198
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2199
|
+
try {
|
|
2200
|
+
const client = getClient();
|
|
2201
|
+
await client.tickets.removeDependency(ticket, options.by);
|
|
2202
|
+
if (useJson) {
|
|
2203
|
+
console.log(JSON.stringify({ ticketId: ticket, removedBlocker: options.by }, null, 2));
|
|
2204
|
+
} else {
|
|
2205
|
+
console.log(`
|
|
2206
|
+
\u2713 Dependency removed: ${ticket} no longer blocked by ${options.by}`);
|
|
2207
|
+
}
|
|
2208
|
+
} catch (error) {
|
|
2209
|
+
handleError(error, useJson);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
);
|
|
2213
|
+
tickets.command("link-action").description("Attach an existing action to a ticket").requiredOption("--id <id>", "Ticket CUID").requiredOption("--action <id>", "Action CUID").action(async (options, cmd) => {
|
|
2214
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2215
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2216
|
+
try {
|
|
2217
|
+
const client = getClient();
|
|
2218
|
+
const action = await client.tickets.linkAction(options.id, options.action);
|
|
2219
|
+
if (useJson) outputActionJson(action);
|
|
2220
|
+
else {
|
|
2221
|
+
console.log("\n\u2713 Action linked to ticket");
|
|
2222
|
+
outputActionPretty(action);
|
|
2223
|
+
}
|
|
2224
|
+
} catch (error) {
|
|
2225
|
+
handleError(error, useJson);
|
|
2226
|
+
}
|
|
2227
|
+
});
|
|
2228
|
+
tickets.command("unlink-action").description("Detach an action from whatever ticket it is on").requiredOption("--action <id>", "Action CUID").action(async (options, cmd) => {
|
|
2229
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2230
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2231
|
+
try {
|
|
2232
|
+
const client = getClient();
|
|
2233
|
+
const action = await client.tickets.unlinkAction(options.action);
|
|
2234
|
+
if (useJson) outputActionJson(action);
|
|
2235
|
+
else {
|
|
2236
|
+
console.log("\n\u2713 Action unlinked");
|
|
2237
|
+
outputActionPretty(action);
|
|
2238
|
+
}
|
|
2239
|
+
} catch (error) {
|
|
2240
|
+
handleError(error, useJson);
|
|
2241
|
+
}
|
|
2242
|
+
});
|
|
2243
|
+
const comment = new Command9("comment").description("Manage comments on a ticket");
|
|
2244
|
+
comment.command("add").description("Add a comment to a ticket").requiredOption("--id <id>", "Ticket CUID").requiredOption("-m, --message <text>", "Comment content (markdown supported)").action(async (options, cmd) => {
|
|
2245
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2246
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2247
|
+
try {
|
|
2248
|
+
const client = getClient();
|
|
2249
|
+
const created = await client.tickets.addComment({
|
|
2250
|
+
ticketId: options.id,
|
|
2251
|
+
content: options.message
|
|
2252
|
+
});
|
|
2253
|
+
if (useJson) outputCommentJson(created);
|
|
2254
|
+
else {
|
|
2255
|
+
console.log("\n\u2713 Comment added");
|
|
2256
|
+
outputCommentPretty(created);
|
|
2257
|
+
}
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
handleError(error, useJson);
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2262
|
+
comment.command("delete").description("Delete one of your own ticket comments").requiredOption("--comment-id <id>", "Comment ID").action(async (options, cmd) => {
|
|
2263
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2264
|
+
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
2265
|
+
try {
|
|
2266
|
+
const client = getClient();
|
|
2267
|
+
await client.tickets.deleteComment(options.commentId);
|
|
2268
|
+
if (useJson) {
|
|
2269
|
+
console.log(JSON.stringify({ deleted: true, id: options.commentId }, null, 2));
|
|
2270
|
+
} else {
|
|
2271
|
+
console.log("\n\u2713 Comment deleted");
|
|
2272
|
+
}
|
|
2273
|
+
} catch (error) {
|
|
2274
|
+
handleError(error, useJson);
|
|
2275
|
+
}
|
|
2276
|
+
});
|
|
2277
|
+
tickets.addCommand(comment);
|
|
2278
|
+
return tickets;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
// src/commands/workspaces.ts
|
|
2282
|
+
import { Command as Command10 } from "commander";
|
|
483
2283
|
import chalk4 from "chalk";
|
|
484
2284
|
function createWorkspacesCommand() {
|
|
485
|
-
const workspaces = new
|
|
2285
|
+
const workspaces = new Command10("workspaces").description("Manage workspaces");
|
|
486
2286
|
workspaces.command("list").description("List all workspaces").action(async (_options, cmd) => {
|
|
487
2287
|
const globalOpts = cmd.optsWithGlobals();
|
|
488
2288
|
const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
|
|
@@ -528,11 +2328,25 @@ function createWorkspacesCommand() {
|
|
|
528
2328
|
}
|
|
529
2329
|
|
|
530
2330
|
// src/index.ts
|
|
531
|
-
var program = new
|
|
532
|
-
program.name("exponential").description(
|
|
2331
|
+
var program = new Command11();
|
|
2332
|
+
program.name("exponential").description(
|
|
2333
|
+
[
|
|
2334
|
+
"CLI to interact with Exponential productivity app.",
|
|
2335
|
+
"",
|
|
2336
|
+
"Work-tracking hierarchy:",
|
|
2337
|
+
" workspace -> product -> feature -> ticket -> action",
|
|
2338
|
+
" epics are workspace-scoped and can group tickets and actions across products."
|
|
2339
|
+
].join("\n")
|
|
2340
|
+
).version("1.0.0").option("--json", "Output as JSON (default when piped)").option("--pretty", "Force pretty-printed output");
|
|
533
2341
|
program.addCommand(createAuthCommand());
|
|
534
2342
|
program.addCommand(createActionsCommand());
|
|
2343
|
+
program.addCommand(createContactsCommand());
|
|
2344
|
+
program.addCommand(createDealsCommand());
|
|
2345
|
+
program.addCommand(createEpicsCommand());
|
|
2346
|
+
program.addCommand(createFeaturesCommand());
|
|
2347
|
+
program.addCommand(createProductsCommand());
|
|
535
2348
|
program.addCommand(createProjectsCommand());
|
|
2349
|
+
program.addCommand(createTicketsCommand());
|
|
536
2350
|
program.addCommand(createWorkspacesCommand());
|
|
537
2351
|
program.parse();
|
|
538
2352
|
//# sourceMappingURL=index.js.map
|