exponential-cli 1.0.0 → 1.2.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/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 Command5 } from "commander";
4
+ import { Command as Command12 } 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,1396 @@ function createActionsCommand() {
450
1116
  handleError(error, useJson);
451
1117
  }
452
1118
  });
453
- return actions;
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 projects2 = await client.projects.list({
466
- workspaceId: options.workspace,
467
- includeActions: options.includeActions
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
- outputProjectsJson(projects2);
1147
+ outputActionJson(action);
471
1148
  } else {
472
- outputProjectsPretty(projects2);
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
- return projects;
479
- }
480
-
481
- // src/commands/workspaces.ts
482
- import { Command as Command4 } from "commander";
483
- import chalk4 from "chalk";
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/labels.ts
1886
+ import { Command as Command7 } from "commander";
1887
+ import chalk4 from "chalk";
1888
+ var TRIAGE_LABELS = [
1889
+ { slug: "needs-triage", name: "needs-triage", color: "avatar-orange" },
1890
+ { slug: "needs-info", name: "needs-info", color: "avatar-yellow" },
1891
+ { slug: "ready-for-agent", name: "ready-for-agent", color: "avatar-green" },
1892
+ { slug: "ready-for-human", name: "ready-for-human", color: "avatar-lightBlue" },
1893
+ { slug: "wontfix", name: "wontfix", color: "avatar-lightGray" }
1894
+ ];
1895
+ var CUID_PATTERN = /^c[a-z0-9]{20,}$/i;
1896
+ function looksLikeCuid(value) {
1897
+ return CUID_PATTERN.test(value);
1898
+ }
1899
+ function outputTagsPretty(tags) {
1900
+ if (tags.length === 0) {
1901
+ console.log(chalk4.dim("No labels found."));
1902
+ return;
1903
+ }
1904
+ console.log();
1905
+ for (const tag of tags) {
1906
+ const scope = tag.workspaceId === null ? chalk4.cyan("global") : chalk4.magenta("workspace");
1907
+ const category = tag.category === null ? chalk4.dim("(label)") : chalk4.dim(`(${tag.category})`);
1908
+ console.log(
1909
+ ` ${chalk4.bold(tag.slug)} ${tag.name} ${chalk4.gray(tag.color)} ${scope} ${category}`
1910
+ );
1911
+ }
1912
+ console.log();
1913
+ }
1914
+ function outputTagPretty(tag, prefix) {
1915
+ if (prefix) console.log(prefix);
1916
+ console.log(` ${chalk4.bold(tag.slug)} ${tag.name} ${chalk4.gray(tag.color)}`);
1917
+ }
1918
+ async function resolveLabelIds(client, workspaceId, values) {
1919
+ if (values.length === 0) return [];
1920
+ const bySlug = values.filter((v) => !looksLikeCuid(v));
1921
+ const byId = values.filter((v) => looksLikeCuid(v));
1922
+ if (bySlug.length === 0) return byId;
1923
+ const list = await client.labels.list({ workspaceId });
1924
+ const slugMap = new Map(list.allTags.map((t) => [t.slug, t.id]));
1925
+ const resolvedFromSlugs = [];
1926
+ const missing = [];
1927
+ for (const slug of bySlug) {
1928
+ const id = slugMap.get(slug);
1929
+ if (id === void 0) missing.push(slug);
1930
+ else resolvedFromSlugs.push(id);
1931
+ }
1932
+ if (missing.length > 0) {
1933
+ throw new Error(
1934
+ `Unknown label slug(s): ${missing.join(", ")}. Run \`exponential labels list --workspace <slug>\` to see available labels.`
1935
+ );
1936
+ }
1937
+ return [...byId, ...resolvedFromSlugs];
1938
+ }
1939
+ function createLabelsCommand() {
1940
+ const labels = new Command7("labels").description(
1941
+ "Manage labels (orthogonal tags applied to actions, tickets, features, and epics)."
1942
+ );
1943
+ labels.command("list").description("List labels visible in a workspace").option("--workspace <slug|id>", "Workspace slug or CUID").option(
1944
+ "--category <category>",
1945
+ 'Filter by category. Use "label" for general labels, "null" for category-less labels, or omit to list all.'
1946
+ ).action(
1947
+ async (options, cmd) => {
1948
+ const globalOpts = cmd.optsWithGlobals();
1949
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
1950
+ try {
1951
+ const client = getClient();
1952
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
1953
+ const category = options.category === void 0 ? void 0 : options.category === "null" ? null : options.category;
1954
+ const result = await client.labels.list({ workspaceId, category });
1955
+ if (useJson) {
1956
+ console.log(JSON.stringify(result.allTags, null, 2));
1957
+ } else {
1958
+ outputTagsPretty(result.allTags);
1959
+ }
1960
+ } catch (error) {
1961
+ handleError(error, useJson);
1962
+ }
1963
+ }
1964
+ );
1965
+ labels.command("create").description("Create a workspace-scoped label").requiredOption("-n, --name <name>", "Label name").requiredOption("--color <token>", "Color token (e.g. avatar-green, brand-primary)").option("-d, --description <text>", "Description").option("--category <category>", 'Category. Omit (or pass "null") to create a general label.').option("--workspace <slug|id>", "Workspace slug or CUID").action(
1966
+ async (options, cmd) => {
1967
+ const globalOpts = cmd.optsWithGlobals();
1968
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
1969
+ try {
1970
+ const client = getClient();
1971
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
1972
+ if (workspaceId === void 0) {
1973
+ throw new Error(
1974
+ "Workspace is required. Pass --workspace <slug|id> or set a default with `exponential workspaces set-default`."
1975
+ );
1976
+ }
1977
+ const category = options.category === void 0 ? void 0 : options.category === "null" ? null : options.category;
1978
+ const tag = await client.labels.create({
1979
+ workspaceId,
1980
+ name: options.name,
1981
+ color: options.color,
1982
+ description: options.description,
1983
+ category
1984
+ });
1985
+ if (useJson) {
1986
+ console.log(JSON.stringify(tag, null, 2));
1987
+ } else {
1988
+ outputTagPretty(tag, chalk4.green("\n\u2713 Label created"));
1989
+ }
1990
+ } catch (error) {
1991
+ handleError(error, useJson);
1992
+ }
1993
+ }
1994
+ );
1995
+ labels.command("seed-triage").description("Idempotently seed the five canonical triage labels into a workspace.").option("--workspace <slug|id>", "Workspace slug or CUID").action(
1996
+ async (options, cmd) => {
1997
+ const globalOpts = cmd.optsWithGlobals();
1998
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
1999
+ try {
2000
+ const client = getClient();
2001
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
2002
+ if (workspaceId === void 0) {
2003
+ throw new Error(
2004
+ "Workspace is required. Pass --workspace <slug|id> or set a default with `exponential workspaces set-default`."
2005
+ );
2006
+ }
2007
+ const existing = await client.labels.list({ workspaceId });
2008
+ const existingSlugs = new Set(existing.allTags.map((t) => t.slug));
2009
+ const results = [];
2010
+ for (const def of TRIAGE_LABELS) {
2011
+ if (existingSlugs.has(def.slug)) {
2012
+ results.push({ slug: def.slug, status: "existed" });
2013
+ continue;
2014
+ }
2015
+ const tag = await client.labels.create({
2016
+ workspaceId,
2017
+ name: def.name,
2018
+ color: def.color
2019
+ });
2020
+ results.push({ slug: def.slug, status: "created", tag });
2021
+ }
2022
+ if (useJson) {
2023
+ console.log(JSON.stringify(results, null, 2));
2024
+ } else {
2025
+ console.log();
2026
+ console.log(chalk4.bold("Triage labels:"));
2027
+ for (const r of results) {
2028
+ const tag = chalk4.bold(r.slug);
2029
+ const status = r.status === "created" ? chalk4.green("created") : chalk4.dim("existed");
2030
+ console.log(` ${tag} ${status}`);
2031
+ }
2032
+ console.log();
2033
+ }
2034
+ } catch (error) {
2035
+ handleError(error, useJson);
2036
+ }
2037
+ }
2038
+ );
2039
+ return labels;
2040
+ }
2041
+
2042
+ // src/commands/products.ts
2043
+ import { Command as Command8 } from "commander";
2044
+ function createProductsCommand() {
2045
+ const products = new Command8("products").description(
2046
+ "Manage products. A workspace contains products; a product holds features and tickets."
2047
+ );
2048
+ 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) => {
2049
+ const globalOpts = cmd.optsWithGlobals();
2050
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2051
+ try {
2052
+ const client = getClient();
2053
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
2054
+ const list = await client.products.list(workspaceId);
2055
+ if (useJson) outputProductsJson(list);
2056
+ else outputProductsPretty(list);
2057
+ } catch (error) {
2058
+ handleError(error, useJson);
2059
+ }
2060
+ });
2061
+ 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) => {
2062
+ const globalOpts = cmd.optsWithGlobals();
2063
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2064
+ try {
2065
+ const client = getClient();
2066
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
2067
+ const product = await client.products.resolve(workspaceId, slugOrId);
2068
+ if (useJson) outputProductJson(product);
2069
+ else outputProductPretty(product);
2070
+ } catch (error) {
2071
+ handleError(error, useJson);
2072
+ }
2073
+ });
2074
+ 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(
2075
+ async (options, cmd) => {
2076
+ const globalOpts = cmd.optsWithGlobals();
2077
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2078
+ try {
2079
+ if (!/^[a-z0-9-]+$/.test(options.slug)) {
2080
+ throw new Error(
2081
+ `Invalid slug "${options.slug}". Slug must be kebab-case (a-z, 0-9, -).`
2082
+ );
2083
+ }
2084
+ const client = getClient();
2085
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
2086
+ const product = await client.products.create({
2087
+ workspaceId,
2088
+ name: options.name,
2089
+ slug: options.slug,
2090
+ description: options.description,
2091
+ icon: options.icon,
2092
+ color: options.color
2093
+ });
2094
+ if (useJson) outputProductJson(product);
2095
+ else {
2096
+ console.log("\n\u2713 Product created");
2097
+ outputProductPretty(product);
2098
+ }
2099
+ } catch (error) {
2100
+ handleError(error, useJson);
2101
+ }
2102
+ }
2103
+ );
2104
+ return products;
2105
+ }
2106
+
2107
+ // src/commands/projects.ts
2108
+ import { Command as Command9 } from "commander";
2109
+ function createProjectsCommand() {
2110
+ const projects = new Command9("projects").description("Manage projects");
2111
+ 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) => {
2112
+ const globalOpts = cmd.optsWithGlobals();
2113
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2114
+ try {
2115
+ const client = getClient();
2116
+ const projects2 = await client.projects.list({
2117
+ workspaceId: options.workspace,
2118
+ includeActions: options.includeActions
2119
+ });
2120
+ if (useJson) {
2121
+ outputProjectsJson(projects2);
2122
+ } else {
2123
+ outputProjectsPretty(projects2);
2124
+ }
2125
+ } catch (error) {
2126
+ handleError(error, useJson);
2127
+ }
2128
+ });
2129
+ return projects;
2130
+ }
2131
+
2132
+ // src/commands/tickets.ts
2133
+ import { Command as Command10 } from "commander";
2134
+ function collectRepeatable(value, previous) {
2135
+ return [...previous, value];
2136
+ }
2137
+ var TICKET_STATUSES = [
2138
+ "BACKLOG",
2139
+ "NEEDS_REFINEMENT",
2140
+ "READY_TO_PLAN",
2141
+ "COMMITTED",
2142
+ "IN_PROGRESS",
2143
+ "BLOCKED",
2144
+ "QA",
2145
+ "DONE",
2146
+ "DEPLOYED",
2147
+ "ARCHIVED"
2148
+ ];
2149
+ var TICKET_TYPES = [
2150
+ "BUG",
2151
+ "FEATURE",
2152
+ "CHORE",
2153
+ "IMPROVEMENT",
2154
+ "SPIKE",
2155
+ "RESEARCH"
2156
+ ];
2157
+ function validateTicketStatus(value) {
2158
+ if (!value) return void 0;
2159
+ if (!TICKET_STATUSES.includes(value)) {
2160
+ throw new Error(
2161
+ `Invalid ticket status "${value}". Valid: ${TICKET_STATUSES.join(", ")}`
2162
+ );
2163
+ }
2164
+ return value;
2165
+ }
2166
+ function validateTicketType(value) {
2167
+ if (!value) return void 0;
2168
+ if (!TICKET_TYPES.includes(value)) {
2169
+ throw new Error(
2170
+ `Invalid ticket type "${value}". Valid: ${TICKET_TYPES.join(", ")}`
2171
+ );
2172
+ }
2173
+ return value;
2174
+ }
2175
+ function createTicketsCommand() {
2176
+ const tickets = new Command10("tickets").description(
2177
+ "Manage product backlog tickets. Tickets live under a product, link to features/epics, depend on each other, and can be implemented via actions."
2178
+ );
2179
+ 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").option(
2180
+ "--label <slug-or-id>",
2181
+ "Filter to tickets carrying this label (repeatable; AND semantics)",
2182
+ collectRepeatable,
2183
+ []
2184
+ ).action(
2185
+ async (options, cmd) => {
2186
+ const globalOpts = cmd.optsWithGlobals();
2187
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2188
+ try {
2189
+ const status = validateTicketStatus(options.status);
2190
+ const type = validateTicketType(options.type);
2191
+ const client = getClient();
2192
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
2193
+ const productId = await resolveProductId(
2194
+ client,
2195
+ workspaceId,
2196
+ options.product
2197
+ );
2198
+ const list = await client.tickets.list({
2199
+ productId,
2200
+ status,
2201
+ type,
2202
+ featureId: options.feature,
2203
+ epicId: options.epic,
2204
+ assigneeId: options.assignee
2205
+ });
2206
+ let filtered = list;
2207
+ if (options.label.length > 0) {
2208
+ const labelIds = new Set(
2209
+ await resolveLabelIds(client, workspaceId, options.label)
2210
+ );
2211
+ const ticketLabelSets = await Promise.all(
2212
+ list.map(
2213
+ (t) => client.labels.listForEntity({ entityType: "ticket", entityId: t.id }).then((tags) => new Set(tags.map((tag) => tag.id)))
2214
+ )
2215
+ );
2216
+ filtered = list.filter((_t, idx) => {
2217
+ const tagSet = ticketLabelSets[idx];
2218
+ if (tagSet === void 0) return false;
2219
+ for (const id of labelIds) {
2220
+ if (!tagSet.has(id)) return false;
2221
+ }
2222
+ return true;
2223
+ });
2224
+ }
2225
+ if (useJson) outputTicketsJson(filtered);
2226
+ else outputTicketsPretty(filtered);
2227
+ } catch (error) {
2228
+ handleError(error, useJson);
2229
+ }
2230
+ }
2231
+ );
2232
+ tickets.command("get <id>").description("Show a ticket with dependencies, actions, and comments").action(async (id, _options, cmd) => {
2233
+ const globalOpts = cmd.optsWithGlobals();
2234
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2235
+ try {
2236
+ const client = getClient();
2237
+ const ticket = await client.tickets.get(id);
2238
+ if (useJson) outputTicketJson(ticket);
2239
+ else outputTicketPretty(ticket);
2240
+ } catch (error) {
2241
+ handleError(error, useJson);
2242
+ }
2243
+ });
2244
+ tickets.command("show <id>").description("Alias for `tickets get`").action(async (id, _options, cmd) => {
2245
+ const globalOpts = cmd.optsWithGlobals();
2246
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2247
+ try {
2248
+ const client = getClient();
2249
+ const ticket = await client.tickets.get(id);
2250
+ if (useJson) outputTicketJson(ticket);
2251
+ else outputTicketPretty(ticket);
2252
+ } catch (error) {
2253
+ handleError(error, useJson);
2254
+ }
2255
+ });
2256
+ 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)").option(
2257
+ "--label <slug-or-id>",
2258
+ "Label to attach (repeatable)",
2259
+ collectRepeatable,
2260
+ []
2261
+ ).action(
2262
+ async (options, cmd) => {
2263
+ const globalOpts = cmd.optsWithGlobals();
2264
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2265
+ try {
2266
+ const status = validateTicketStatus(options.status);
2267
+ const type = validateTicketType(options.type);
2268
+ const client = getClient();
2269
+ const workspaceId = await resolveWorkspaceId(client, options.workspace);
2270
+ const productId = await resolveProductId(
2271
+ client,
2272
+ workspaceId,
2273
+ options.product
2274
+ );
2275
+ const ticket = await client.tickets.create({
2276
+ productId,
2277
+ title: options.title,
2278
+ body: options.body,
2279
+ type,
2280
+ status,
2281
+ priority: options.priority,
2282
+ points: options.points,
2283
+ featureId: options.feature,
2284
+ epicId: options.epic,
2285
+ assigneeId: options.assignee,
2286
+ branchName: options.branch,
2287
+ prUrl: options.pr
2288
+ });
2289
+ if (options.label.length > 0) {
2290
+ const tagIds = await resolveLabelIds(client, workspaceId, options.label);
2291
+ await client.labels.setEntityTags({
2292
+ entityType: "ticket",
2293
+ entityId: ticket.id,
2294
+ tagIds
2295
+ });
2296
+ }
2297
+ if (useJson) outputTicketJson(ticket);
2298
+ else {
2299
+ console.log("\n\u2713 Ticket created");
2300
+ outputTicketPretty(ticket);
2301
+ }
2302
+ } catch (error) {
2303
+ handleError(error, useJson);
2304
+ }
2305
+ }
2306
+ );
2307
+ 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")').option(
2308
+ "--add-label <slug-or-id>",
2309
+ "Label to attach to this ticket (repeatable)",
2310
+ collectRepeatable,
2311
+ []
2312
+ ).option(
2313
+ "--remove-label <slug-or-id>",
2314
+ "Label to detach from this ticket (repeatable)",
2315
+ collectRepeatable,
2316
+ []
2317
+ ).action(
2318
+ async (options, cmd) => {
2319
+ const globalOpts = cmd.optsWithGlobals();
2320
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2321
+ try {
2322
+ const parseNullableInt = (v) => {
2323
+ if (v === void 0) return void 0;
2324
+ if (v === "null") return null;
2325
+ const n = parseInt(v, 10);
2326
+ if (isNaN(n)) throw new Error(`Invalid number: ${v}`);
2327
+ return n;
2328
+ };
2329
+ const parseNullableFloat = (v) => {
2330
+ if (v === void 0) return void 0;
2331
+ if (v === "null") return null;
2332
+ const n = parseFloat(v);
2333
+ if (isNaN(n)) throw new Error(`Invalid number: ${v}`);
2334
+ return n;
2335
+ };
2336
+ const parseNullableString = (v) => {
2337
+ if (v === void 0) return void 0;
2338
+ if (v === "null") return null;
2339
+ return v;
2340
+ };
2341
+ const status = validateTicketStatus(options.status);
2342
+ const type = validateTicketType(options.type);
2343
+ const client = getClient();
2344
+ const ticket = await client.tickets.update({
2345
+ id: options.id,
2346
+ title: options.title,
2347
+ body: options.body,
2348
+ type,
2349
+ status,
2350
+ priority: parseNullableInt(options.priority),
2351
+ points: parseNullableFloat(options.points),
2352
+ featureId: parseNullableString(options.feature),
2353
+ epicId: parseNullableString(options.epic),
2354
+ assigneeId: parseNullableString(options.assignee),
2355
+ branchName: parseNullableString(options.branch),
2356
+ prUrl: parseNullableString(options.pr)
2357
+ });
2358
+ if (options.addLabel.length > 0 || options.removeLabel.length > 0) {
2359
+ const current = await client.labels.listForEntity({
2360
+ entityType: "ticket",
2361
+ entityId: options.id
2362
+ });
2363
+ const currentIds = new Set(current.map((t) => t.id));
2364
+ const addIds = await resolveLabelIds(client, void 0, options.addLabel);
2365
+ const removeIds = await resolveLabelIds(client, void 0, options.removeLabel);
2366
+ for (const id of addIds) currentIds.add(id);
2367
+ for (const id of removeIds) currentIds.delete(id);
2368
+ await client.labels.setEntityTags({
2369
+ entityType: "ticket",
2370
+ entityId: options.id,
2371
+ tagIds: [...currentIds]
2372
+ });
2373
+ }
2374
+ if (useJson) outputTicketJson(ticket);
2375
+ else {
2376
+ console.log("\n\u2713 Ticket updated");
2377
+ outputTicketPretty(ticket);
2378
+ }
2379
+ } catch (error) {
2380
+ handleError(error, useJson);
2381
+ }
2382
+ }
2383
+ );
2384
+ tickets.command("delete <id>").description("Delete a ticket").action(async (id, _options, cmd) => {
2385
+ const globalOpts = cmd.optsWithGlobals();
2386
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2387
+ try {
2388
+ const client = getClient();
2389
+ await client.tickets.delete(id);
2390
+ if (useJson) {
2391
+ console.log(JSON.stringify({ deleted: true, id }, null, 2));
2392
+ } else {
2393
+ console.log("\n\u2713 Ticket deleted");
2394
+ }
2395
+ } catch (error) {
2396
+ handleError(error, useJson);
2397
+ }
2398
+ });
2399
+ tickets.command("block <ticket>").description("Mark <ticket> as blocked by another ticket").requiredOption("--by <id>", "Ticket CUID that must complete first").action(
2400
+ async (ticket, options, cmd) => {
2401
+ const globalOpts = cmd.optsWithGlobals();
2402
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2403
+ try {
2404
+ const client = getClient();
2405
+ const dep = await client.tickets.addDependency(ticket, options.by);
2406
+ if (useJson) {
2407
+ console.log(JSON.stringify(dep, null, 2));
2408
+ } else {
2409
+ const depId = dep.dependsOn.shortId ?? (dep.dependsOn.number != null ? `#${dep.dependsOn.number}` : dep.dependsOn.id);
2410
+ console.log(`
2411
+ \u2713 Ticket ${ticket} now depends on ${depId} ${dep.dependsOn.title}`);
2412
+ }
2413
+ } catch (error) {
2414
+ handleError(error, useJson);
2415
+ }
2416
+ }
2417
+ );
2418
+ tickets.command("unblock <ticket>").description("Remove a blocking dependency from <ticket>").requiredOption("--by <id>", "The ticket CUID that was blocking").action(
2419
+ async (ticket, options, cmd) => {
2420
+ const globalOpts = cmd.optsWithGlobals();
2421
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2422
+ try {
2423
+ const client = getClient();
2424
+ await client.tickets.removeDependency(ticket, options.by);
2425
+ if (useJson) {
2426
+ console.log(JSON.stringify({ ticketId: ticket, removedBlocker: options.by }, null, 2));
2427
+ } else {
2428
+ console.log(`
2429
+ \u2713 Dependency removed: ${ticket} no longer blocked by ${options.by}`);
2430
+ }
2431
+ } catch (error) {
2432
+ handleError(error, useJson);
2433
+ }
2434
+ }
2435
+ );
2436
+ 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) => {
2437
+ const globalOpts = cmd.optsWithGlobals();
2438
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2439
+ try {
2440
+ const client = getClient();
2441
+ const action = await client.tickets.linkAction(options.id, options.action);
2442
+ if (useJson) outputActionJson(action);
2443
+ else {
2444
+ console.log("\n\u2713 Action linked to ticket");
2445
+ outputActionPretty(action);
2446
+ }
2447
+ } catch (error) {
2448
+ handleError(error, useJson);
2449
+ }
2450
+ });
2451
+ tickets.command("unlink-action").description("Detach an action from whatever ticket it is on").requiredOption("--action <id>", "Action CUID").action(async (options, cmd) => {
2452
+ const globalOpts = cmd.optsWithGlobals();
2453
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2454
+ try {
2455
+ const client = getClient();
2456
+ const action = await client.tickets.unlinkAction(options.action);
2457
+ if (useJson) outputActionJson(action);
2458
+ else {
2459
+ console.log("\n\u2713 Action unlinked");
2460
+ outputActionPretty(action);
2461
+ }
2462
+ } catch (error) {
2463
+ handleError(error, useJson);
2464
+ }
2465
+ });
2466
+ const comment = new Command10("comment").description("Manage comments on a ticket");
2467
+ 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) => {
2468
+ const globalOpts = cmd.optsWithGlobals();
2469
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2470
+ try {
2471
+ const client = getClient();
2472
+ const created = await client.tickets.addComment({
2473
+ ticketId: options.id,
2474
+ content: options.message
2475
+ });
2476
+ if (useJson) outputCommentJson(created);
2477
+ else {
2478
+ console.log("\n\u2713 Comment added");
2479
+ outputCommentPretty(created);
2480
+ }
2481
+ } catch (error) {
2482
+ handleError(error, useJson);
2483
+ }
2484
+ });
2485
+ comment.command("delete").description("Delete one of your own ticket comments").requiredOption("--comment-id <id>", "Comment ID").action(async (options, cmd) => {
2486
+ const globalOpts = cmd.optsWithGlobals();
2487
+ const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
2488
+ try {
2489
+ const client = getClient();
2490
+ await client.tickets.deleteComment(options.commentId);
2491
+ if (useJson) {
2492
+ console.log(JSON.stringify({ deleted: true, id: options.commentId }, null, 2));
2493
+ } else {
2494
+ console.log("\n\u2713 Comment deleted");
2495
+ }
2496
+ } catch (error) {
2497
+ handleError(error, useJson);
2498
+ }
2499
+ });
2500
+ tickets.addCommand(comment);
2501
+ return tickets;
2502
+ }
2503
+
2504
+ // src/commands/workspaces.ts
2505
+ import { Command as Command11 } from "commander";
2506
+ import chalk5 from "chalk";
484
2507
  function createWorkspacesCommand() {
485
- const workspaces = new Command4("workspaces").description("Manage workspaces");
2508
+ const workspaces = new Command11("workspaces").description("Manage workspaces");
486
2509
  workspaces.command("list").description("List all workspaces").action(async (_options, cmd) => {
487
2510
  const globalOpts = cmd.optsWithGlobals();
488
2511
  const useJson = shouldUseJson(globalOpts.json, globalOpts.pretty);
@@ -495,7 +2518,7 @@ function createWorkspacesCommand() {
495
2518
  const config = getConfig();
496
2519
  outputWorkspacesPretty(workspaces2);
497
2520
  if (config.defaultWorkspaceSlug) {
498
- console.log(chalk4.gray(`Default workspace: ${config.defaultWorkspaceSlug}`));
2521
+ console.log(chalk5.gray(`Default workspace: ${config.defaultWorkspaceSlug}`));
499
2522
  }
500
2523
  }
501
2524
  } catch (error) {
@@ -508,10 +2531,10 @@ function createWorkspacesCommand() {
508
2531
  const workspaces2 = await client.workspaces.list();
509
2532
  const workspace = workspaces2.find((w) => w.slug === slug);
510
2533
  if (!workspace) {
511
- console.error(chalk4.red(`Workspace with slug "${slug}" not found.`));
512
- console.log(chalk4.gray("Available workspaces:"));
2534
+ console.error(chalk5.red(`Workspace with slug "${slug}" not found.`));
2535
+ console.log(chalk5.gray("Available workspaces:"));
513
2536
  workspaces2.forEach((w) => {
514
- console.log(chalk4.gray(` - ${w.slug} (${w.name})`));
2537
+ console.log(chalk5.gray(` - ${w.slug} (${w.name})`));
515
2538
  });
516
2539
  process.exit(1);
517
2540
  }
@@ -519,7 +2542,7 @@ function createWorkspacesCommand() {
519
2542
  defaultWorkspaceId: workspace.id,
520
2543
  defaultWorkspaceSlug: workspace.slug
521
2544
  });
522
- console.log(chalk4.green(`Default workspace set to: ${workspace.name} (${workspace.slug})`));
2545
+ console.log(chalk5.green(`Default workspace set to: ${workspace.name} (${workspace.slug})`));
523
2546
  } catch (error) {
524
2547
  handleError(error);
525
2548
  }
@@ -528,11 +2551,26 @@ function createWorkspacesCommand() {
528
2551
  }
529
2552
 
530
2553
  // src/index.ts
531
- var program = new Command5();
532
- program.name("exponential").description("CLI to interact with Exponential productivity app").version("1.0.0").option("--json", "Output as JSON (default when piped)").option("--pretty", "Force pretty-printed output");
2554
+ var program = new Command12();
2555
+ program.name("exponential").description(
2556
+ [
2557
+ "CLI to interact with Exponential productivity app.",
2558
+ "",
2559
+ "Work-tracking hierarchy:",
2560
+ " workspace -> product -> feature -> ticket -> action",
2561
+ " epics are workspace-scoped and can group tickets and actions across products."
2562
+ ].join("\n")
2563
+ ).version("1.0.0").option("--json", "Output as JSON (default when piped)").option("--pretty", "Force pretty-printed output");
533
2564
  program.addCommand(createAuthCommand());
534
2565
  program.addCommand(createActionsCommand());
2566
+ program.addCommand(createContactsCommand());
2567
+ program.addCommand(createDealsCommand());
2568
+ program.addCommand(createEpicsCommand());
2569
+ program.addCommand(createFeaturesCommand());
2570
+ program.addCommand(createLabelsCommand());
2571
+ program.addCommand(createProductsCommand());
535
2572
  program.addCommand(createProjectsCommand());
2573
+ program.addCommand(createTicketsCommand());
536
2574
  program.addCommand(createWorkspacesCommand());
537
2575
  program.parse();
538
2576
  //# sourceMappingURL=index.js.map