@atollhq/cli 0.1.3 → 0.1.4

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
@@ -81,9 +81,9 @@ var init_config = __esm({
81
81
  });
82
82
 
83
83
  // src/index.ts
84
- var import_commander8 = require("commander");
85
- var import_node_fs2 = require("fs");
86
- var import_node_path2 = require("path");
84
+ var import_commander11 = require("commander");
85
+ var import_node_fs4 = require("fs");
86
+ var import_node_path4 = require("path");
87
87
 
88
88
  // src/commands/auth.ts
89
89
  var import_commander = require("commander");
@@ -97,6 +97,9 @@ function isJsonMode() {
97
97
  if (process.env.OUTPUT_FORMAT === "json") return true;
98
98
  return !process.stdout.isTTY;
99
99
  }
100
+ function enableJsonMode() {
101
+ process.env.OUTPUT_FORMAT = "json";
102
+ }
100
103
  function output(data, humanReadable) {
101
104
  if (isJsonMode()) {
102
105
  process.stdout.write(JSON.stringify(data) + "\n");
@@ -104,13 +107,34 @@ function output(data, humanReadable) {
104
107
  console.log(humanReadable);
105
108
  }
106
109
  }
110
+ function outputJson(data) {
111
+ process.stdout.write(JSON.stringify(data) + "\n");
112
+ }
107
113
  function outputError(message) {
108
114
  if (isJsonMode()) {
109
- process.stdout.write(JSON.stringify({ error: message }) + "\n");
115
+ process.stderr.write(JSON.stringify({ error: message }) + "\n");
110
116
  } else {
111
117
  console.error(`Error: ${message}`);
112
118
  }
113
119
  }
120
+ var DEFAULT_LIST_LIMIT = 25;
121
+ var MAX_LIST_LIMIT = 100;
122
+ function normalizeLimit(limit, defaultLimit = DEFAULT_LIST_LIMIT) {
123
+ if (limit === void 0 || Number.isNaN(limit)) return defaultLimit;
124
+ if (!Number.isInteger(limit) || limit < 1 || limit > MAX_LIST_LIMIT) {
125
+ outputError(`--limit must be an integer from 1 to ${MAX_LIST_LIMIT} (got: "${limit}")`);
126
+ process.exit(2);
127
+ }
128
+ return limit;
129
+ }
130
+ function normalizeOffset(offset) {
131
+ if (offset === void 0 || Number.isNaN(offset)) return 0;
132
+ if (!Number.isInteger(offset) || offset < 0) {
133
+ outputError(`--offset must be a non-negative integer (got: "${offset}")`);
134
+ process.exit(2);
135
+ }
136
+ return offset;
137
+ }
114
138
 
115
139
  // src/lib/client.ts
116
140
  var DEFAULT_BASE_URL = "https://atollhq.com";
@@ -140,7 +164,10 @@ var AtollClient = class {
140
164
  const body = await res.text();
141
165
  throw new Error(`API ${res.status}: ${body}`);
142
166
  }
143
- return res.json();
167
+ if (res.status === 204) return void 0;
168
+ const text = await res.text();
169
+ if (!text) return void 0;
170
+ return JSON.parse(text);
144
171
  }
145
172
  async get(path) {
146
173
  return this.request(path, { method: "GET" });
@@ -464,15 +491,26 @@ Examples:
464
491
  $ atoll issue create --title "Fix login" --priority 1 --status todo
465
492
  $ atoll issue update ATOLL-42 --status done
466
493
  $ atoll issue assign ATOLL-42 --to <user-id>`);
467
- issueCommand.command("list").description("List issues").option("--status <status>", `Filter by status (${VALID_STATUSES.join(", ")})`).option("--assignee <user>", "Filter by assignee ID").option("--priority <n>", "Filter by priority (0=urgent, 1=high, 2=medium, 3=low)", parseInt).option("--limit <n>", "Max results (1-100)", parseInt).action(async (opts) => {
494
+ issueCommand.command("list").description("List issues").option("--status <status>", `Filter by status (${VALID_STATUSES.join(", ")})`).option("--assignee <user>", "Filter by assignee ID").option("--priority <n>", "Filter by priority (0=urgent, 1=high, 2=medium, 3=low)", parseInt).option("--limit <n>", "Max results (1-100)", parseInt).option("--offset <n>", "Results offset for pagination", parseInt).action(async (opts) => {
468
495
  try {
496
+ if (opts.status && !VALID_STATUSES.includes(opts.status)) {
497
+ outputError(`Invalid status "${opts.status}". Must be one of: ${VALID_STATUSES.join(", ")}`);
498
+ process.exit(2);
499
+ }
500
+ if (opts.priority !== void 0 && ![0, 1, 2, 3].includes(opts.priority)) {
501
+ outputError("Priority must be 0 (urgent), 1 (high), 2 (medium), or 3 (low).");
502
+ process.exit(2);
503
+ }
469
504
  const client = new AtollClient();
470
505
  const org = await resolveOrg(client);
506
+ const limit = normalizeLimit(opts.limit);
507
+ const offset = normalizeOffset(opts.offset);
471
508
  const params = new URLSearchParams();
472
509
  if (opts.status) params.set("status", opts.status);
473
510
  if (opts.assignee) params.set("assigneeId", opts.assignee);
474
511
  if (opts.priority !== void 0) params.set("priority", String(opts.priority));
475
- if (opts.limit !== void 0) params.set("limit", String(opts.limit));
512
+ params.set("limit", String(limit));
513
+ if (offset > 0) params.set("offset", String(offset));
476
514
  const qs = params.toString();
477
515
  const [data, projects] = await Promise.all([
478
516
  client.get(`/api/orgs/${org.id}/issues${qs ? `?${qs}` : ""}`),
@@ -481,10 +519,17 @@ issueCommand.command("list").description("List issues").option("--status <status
481
519
  const enriched = data.issues.map(
482
520
  (issue) => attachIssueUrl(issue, client.baseUrl, org.slug, projects)
483
521
  );
484
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
485
- for (const issue of enriched) {
486
- process.stdout.write(JSON.stringify(issue) + "\n");
487
- }
522
+ if (isJsonMode()) {
523
+ outputJson({
524
+ resource: "issues",
525
+ items: enriched,
526
+ total: data.total,
527
+ limit: data.limit,
528
+ offset: data.offset,
529
+ nextOffset: data.offset + data.limit < data.total ? data.offset + data.limit : null,
530
+ truncated: data.offset + data.limit < data.total,
531
+ hint: data.offset + data.limit < data.total ? "Use --offset with nextOffset to continue." : null
532
+ });
488
533
  return;
489
534
  }
490
535
  if (enriched.length === 0) {
@@ -502,11 +547,14 @@ issueCommand.command("list").description("List issues").option("--status <status
502
547
  console.log(`${padEnd(id, 10)} ${statusColor(issue.status, padEnd(issue.status, 14))} ${icon} ${padEnd(pri, 8)} ${title}`);
503
548
  }
504
549
  console.log(dim(`${enriched.length} of ${data.total} issues`));
550
+ if (data.offset + data.limit < data.total) {
551
+ console.log(dim(`Next: atoll issue list --offset ${data.offset + data.limit} --limit ${data.limit}`));
552
+ }
505
553
  } catch (err) {
506
554
  handleApiError(err);
507
555
  }
508
556
  });
509
- issueCommand.command("view <identifier>").description("View issue details (UUID or PREFIX-NUMBER e.g. ATOLL-42)").action(async (identifier) => {
557
+ async function viewIssue(identifier) {
510
558
  try {
511
559
  const client = new AtollClient();
512
560
  const org = await resolveOrg(client);
@@ -516,8 +564,8 @@ issueCommand.command("view <identifier>").description("View issue details (UUID
516
564
  fetchProjectMap(client, org.id)
517
565
  ]);
518
566
  const enriched = attachIssueUrl(issue, client.baseUrl, org.slug, projects);
519
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
520
- process.stdout.write(JSON.stringify(enriched) + "\n");
567
+ if (isJsonMode()) {
568
+ outputJson(enriched);
521
569
  return;
522
570
  }
523
571
  const id = formatIdentifier(enriched, projects);
@@ -544,7 +592,9 @@ Subtasks: ${enriched.sub_tasks.length}`);
544
592
  } catch (err) {
545
593
  handleApiError(err);
546
594
  }
547
- });
595
+ }
596
+ issueCommand.command("view <identifier>").description("View issue details (UUID or PREFIX-NUMBER e.g. ATOLL-42)").action(viewIssue);
597
+ issueCommand.command("get <identifier>").description("Get issue details (UUID or PREFIX-NUMBER e.g. ATOLL-42)").action(viewIssue);
548
598
  issueCommand.command("create").description("Create a new issue").requiredOption("--title <title>", "Issue title").option("--description <text>", "Issue description").option("--status <status>", `Status (${VALID_STATUSES.join(", ")})`).option("--priority <n>", "Priority (0=urgent, 1=high, 2=medium, 3=low)", parseInt).action(async (opts) => {
549
599
  try {
550
600
  if (opts.status && !VALID_STATUSES.includes(opts.status)) {
@@ -605,11 +655,22 @@ issueCommand.command("update <identifier>").description("Update an issue").optio
605
655
  handleApiError(err);
606
656
  }
607
657
  });
608
- issueCommand.command("delete <identifier>").description("Delete an issue (admin/owner only)").action(async (identifier) => {
658
+ issueCommand.command("delete <identifier>").description("Delete an issue (admin/owner only)").option("--force", "Permanently delete the issue").option("--dry-run", "Show what would be deleted without changing anything").action(async (identifier, opts) => {
609
659
  try {
660
+ if (!opts.dryRun && !opts.force) {
661
+ outputError("Permanent issue deletion requires --force. Prefer `atoll issue archive <identifier>` for reversible removal.");
662
+ process.exit(2);
663
+ }
610
664
  const client = new AtollClient();
611
665
  const orgId = await resolveOrgId(client);
612
666
  const issueId = await resolveIssueId(client, orgId, identifier);
667
+ if (opts.dryRun) {
668
+ output(
669
+ { dryRun: true, wouldDelete: { type: "issue", id: issueId, identifier } },
670
+ `Would permanently delete issue ${identifier} (${issueId})`
671
+ );
672
+ return;
673
+ }
613
674
  await client.delete(`/api/orgs/${orgId}/issues/${issueId}`);
614
675
  output(
615
676
  { success: true, id: issueId },
@@ -619,6 +680,48 @@ issueCommand.command("delete <identifier>").description("Delete an issue (admin/
619
680
  handleApiError(err);
620
681
  }
621
682
  });
683
+ issueCommand.command("archive <identifier>").description("Archive an issue (reversible soft delete)").option("--dry-run", "Show what would be archived without changing anything").action(async (identifier, opts) => {
684
+ try {
685
+ const client = new AtollClient();
686
+ const orgId = await resolveOrgId(client);
687
+ const issueId = await resolveIssueId(client, orgId, identifier);
688
+ if (opts.dryRun) {
689
+ output(
690
+ { dryRun: true, wouldArchive: { type: "issue", id: issueId, identifier } },
691
+ `Would archive issue ${identifier} (${issueId})`
692
+ );
693
+ return;
694
+ }
695
+ await client.post(`/api/orgs/${orgId}/issues/${issueId}/archive`);
696
+ output(
697
+ { success: true, id: issueId, archived: true },
698
+ success(`Archived issue ${identifier}`)
699
+ );
700
+ } catch (err) {
701
+ handleApiError(err);
702
+ }
703
+ });
704
+ issueCommand.command("unarchive <identifier>").description("Unarchive an issue").option("--dry-run", "Show what would be unarchived without changing anything").action(async (identifier, opts) => {
705
+ try {
706
+ const client = new AtollClient();
707
+ const orgId = await resolveOrgId(client);
708
+ const issueId = await resolveIssueId(client, orgId, identifier);
709
+ if (opts.dryRun) {
710
+ output(
711
+ { dryRun: true, wouldUnarchive: { type: "issue", id: issueId, identifier } },
712
+ `Would unarchive issue ${identifier} (${issueId})`
713
+ );
714
+ return;
715
+ }
716
+ await client.delete(`/api/orgs/${orgId}/issues/${issueId}/archive`);
717
+ output(
718
+ { success: true, id: issueId, archived: false },
719
+ success(`Unarchived issue ${identifier}`)
720
+ );
721
+ } catch (err) {
722
+ handleApiError(err);
723
+ }
724
+ });
622
725
  issueCommand.command("assign <identifier>").description("Assign an issue to a user or agent").requiredOption("--to <user>", 'User/agent ID or "self" for yourself').action(async (identifier, opts) => {
623
726
  try {
624
727
  const client = new AtollClient();
@@ -693,32 +796,44 @@ function formatTimestamp(ts) {
693
796
  return d.toLocaleString();
694
797
  }
695
798
  var commentCommand = new import_commander3.Command("comment").description("Manage issue comments");
696
- commentCommand.command("list <identifier>").description("List comments on an issue (UUID or PREFIX-NUMBER e.g. ATOLL-42)").action(async (identifier) => {
799
+ commentCommand.command("list <identifier>").description("List comments on an issue (UUID or PREFIX-NUMBER e.g. ATOLL-42)").option("--limit <n>", "Max results (1-100)", parseInt).action(async (identifier, opts) => {
697
800
  try {
801
+ const limit = normalizeLimit(opts.limit);
698
802
  const client = new AtollClient();
699
803
  const orgId = await resolveOrgId(client);
700
804
  const issueId = await resolveIssueId(client, orgId, identifier);
701
805
  const data = await client.get(
702
806
  `/api/orgs/${orgId}/issues/${issueId}/comments`
703
807
  );
704
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
705
- for (const comment of data.comments) {
706
- process.stdout.write(JSON.stringify(comment) + "\n");
707
- }
808
+ const allComments = data.comments ?? [];
809
+ const comments = allComments.slice(0, limit);
810
+ const truncated = allComments.length > comments.length;
811
+ if (isJsonMode()) {
812
+ outputJson({
813
+ resource: "comments",
814
+ items: comments,
815
+ total: allComments.length,
816
+ limit,
817
+ offset: 0,
818
+ nextOffset: null,
819
+ truncated,
820
+ hint: truncated ? "Increase --limit up to 100 to show more comments." : null
821
+ });
708
822
  return;
709
823
  }
710
- if (data.comments.length === 0) {
824
+ if (comments.length === 0) {
711
825
  console.log("No comments found.");
712
826
  return;
713
827
  }
714
- for (const comment of data.comments) {
828
+ for (const comment of comments) {
715
829
  const author = formatAuthor(comment);
716
830
  const time = formatTimestamp(comment.created_at);
717
831
  console.log(`${BOLD2}${CYAN}${author}${RESET2} ${DIM2}${time}${RESET2}`);
718
832
  console.log(comment.body);
719
833
  console.log();
720
834
  }
721
- console.log(`${DIM2}${data.comments.length} comment(s)${RESET2}`);
835
+ console.log(`${DIM2}${comments.length} of ${allComments.length} comment(s)${RESET2}`);
836
+ if (truncated) console.log(`${DIM2}${allComments.length - comments.length} more comment(s) hidden; increase --limit to show more.${RESET2}`);
722
837
  } catch (err) {
723
838
  handleApiError(err);
724
839
  }
@@ -757,11 +872,22 @@ commentCommand.command("update <comment-id>").description("Update a comment").re
757
872
  handleApiError(err);
758
873
  }
759
874
  });
760
- commentCommand.command("delete <comment-id>").description("Delete a comment").requiredOption("--issue <identifier>", "Issue identifier (e.g. UUID or PREFIX-NUMBER)").action(async (commentId, opts) => {
875
+ commentCommand.command("delete <comment-id>").description("Delete a comment").requiredOption("--issue <identifier>", "Issue identifier (e.g. UUID or PREFIX-NUMBER)").option("--force", "Delete the comment").option("--dry-run", "Show what would be deleted without changing anything").action(async (commentId, opts) => {
761
876
  try {
877
+ if (!opts.dryRun && !opts.force) {
878
+ outputError("Comment deletion requires --force.");
879
+ process.exit(2);
880
+ }
762
881
  const client = new AtollClient();
763
882
  const orgId = await resolveOrgId(client);
764
883
  const issueId = await resolveIssueId(client, orgId, opts.issue);
884
+ if (opts.dryRun) {
885
+ output(
886
+ { dryRun: true, wouldDelete: { type: "comment", id: commentId, issueId } },
887
+ `Would delete comment ${commentId} on ${opts.issue}`
888
+ );
889
+ return;
890
+ }
765
891
  await client.delete(`/api/orgs/${orgId}/issues/${issueId}/comments/${commentId}`);
766
892
  output(
767
893
  { success: true, id: commentId },
@@ -787,16 +913,26 @@ Examples:
787
913
  $ atoll project list
788
914
  $ atoll project create --name "My Project" --icon \u{1F680}
789
915
  $ atoll project view <project-id>`);
790
- projectCommand.command("list").description("List all projects with progress").action(async () => {
916
+ projectCommand.command("list").description("List all projects with progress").option("--limit <n>", "Max results (1-100)", parseInt).action(async (opts) => {
791
917
  const client = new AtollClient();
792
918
  try {
919
+ const limit = normalizeLimit(opts.limit);
793
920
  const org = await resolveOrg(client);
794
921
  const data = await client.get(`/api/orgs/${org.id}/projects`);
795
- const projects = (data.projects ?? []).map((p) => attachProjectUrl(p, client.baseUrl, org.slug));
796
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
797
- for (const p of projects) {
798
- process.stdout.write(JSON.stringify(p) + "\n");
799
- }
922
+ const allProjects = (data.projects ?? []).map((p) => attachProjectUrl(p, client.baseUrl, org.slug));
923
+ const projects = allProjects.slice(0, limit);
924
+ const truncated = allProjects.length > projects.length;
925
+ if (isJsonMode()) {
926
+ outputJson({
927
+ resource: "projects",
928
+ items: projects,
929
+ total: allProjects.length,
930
+ limit,
931
+ offset: 0,
932
+ nextOffset: null,
933
+ truncated,
934
+ hint: truncated ? "Narrow the request in the API or increase --limit up to 100." : null
935
+ });
800
936
  return;
801
937
  }
802
938
  if (projects.length === 0) {
@@ -813,6 +949,7 @@ projectCommand.command("list").description("List all projects with progress").ac
813
949
  if (p.status === "archived") console.log(` ${dim("[archived]")}`);
814
950
  console.log("");
815
951
  }
952
+ if (truncated) console.log(dim(`${allProjects.length - projects.length} more project(s) hidden; increase --limit to show more.`));
816
953
  } catch (err) {
817
954
  handleApiError(err);
818
955
  }
@@ -837,7 +974,7 @@ ${project.url}` : ""}`)
837
974
  handleApiError(err);
838
975
  }
839
976
  });
840
- projectCommand.command("view <projectId>").description("View project details and issues").action(async (projectId) => {
977
+ async function viewProject(projectId) {
841
978
  const client = new AtollClient();
842
979
  try {
843
980
  const org = await resolveOrg(client);
@@ -845,8 +982,8 @@ projectCommand.command("view <projectId>").description("View project details and
845
982
  `/api/orgs/${org.id}/projects/${projectId}`
846
983
  );
847
984
  const p = attachProjectUrl(data.project, client.baseUrl, org.slug);
848
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
849
- process.stdout.write(JSON.stringify(p) + "\n");
985
+ if (isJsonMode()) {
986
+ outputJson(p);
850
987
  return;
851
988
  }
852
989
  const progress = p.progress ?? 0;
@@ -869,7 +1006,9 @@ projectCommand.command("view <projectId>").description("View project details and
869
1006
  } catch (err) {
870
1007
  handleApiError(err);
871
1008
  }
872
- });
1009
+ }
1010
+ projectCommand.command("view <projectId>").description("View project details and issues").action(viewProject);
1011
+ projectCommand.command("get <projectId>").description("Get project details and issues").action(viewProject);
873
1012
 
874
1013
  // src/commands/milestone.ts
875
1014
  var import_commander5 = require("commander");
@@ -882,18 +1021,28 @@ var milestoneCommand = new import_commander5.Command("milestone").description("M
882
1021
  Examples:
883
1022
  $ atoll milestone list --project <project-id>
884
1023
  $ atoll milestone create --project <project-id> --name "v1.0" --date 2026-06-01`);
885
- milestoneCommand.command("list").description("List milestones for a project").requiredOption("--project <id>", "Project ID").action(async (opts) => {
1024
+ milestoneCommand.command("list").description("List milestones for a project").requiredOption("--project <id>", "Project ID").option("--limit <n>", "Max results (1-100)", parseInt).action(async (opts) => {
886
1025
  const client = new AtollClient();
887
1026
  try {
1027
+ const limit = normalizeLimit(opts.limit);
888
1028
  const orgId = await resolveOrgId(client);
889
1029
  const data = await client.get(
890
1030
  `/api/orgs/${orgId}/projects/${opts.project}/milestones`
891
1031
  );
892
- const milestones = data.milestones ?? [];
893
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
894
- for (const m of milestones) {
895
- process.stdout.write(JSON.stringify(m) + "\n");
896
- }
1032
+ const allMilestones = data.milestones ?? [];
1033
+ const milestones = allMilestones.slice(0, limit);
1034
+ const truncated = allMilestones.length > milestones.length;
1035
+ if (isJsonMode()) {
1036
+ outputJson({
1037
+ resource: "milestones",
1038
+ items: milestones,
1039
+ total: allMilestones.length,
1040
+ limit,
1041
+ offset: 0,
1042
+ nextOffset: null,
1043
+ truncated,
1044
+ hint: truncated ? "Narrow the request by project or increase --limit up to 100." : null
1045
+ });
897
1046
  return;
898
1047
  }
899
1048
  if (milestones.length === 0) {
@@ -911,6 +1060,7 @@ milestoneCommand.command("list").description("List milestones for a project").re
911
1060
  if (m.status === "closed") console.log(` ${dim("[closed]")}`);
912
1061
  console.log("");
913
1062
  }
1063
+ if (truncated) console.log(dim(`${allMilestones.length - milestones.length} more milestone(s) hidden; increase --limit to show more.`));
914
1064
  } catch (err) {
915
1065
  handleApiError(err);
916
1066
  }
@@ -1059,32 +1209,44 @@ Examples:
1059
1209
  $ atoll webhook list
1060
1210
  $ atoll webhook create --url https://example.com/hook --events issue.created,issue.updated
1061
1211
  $ atoll webhook delete <id>`);
1062
- webhookCommand.command("list").description("List webhooks for the current org").action(async () => {
1212
+ webhookCommand.command("list").description("List webhooks for the current org").option("--limit <n>", "Max results (1-100)", parseInt).action(async (opts) => {
1063
1213
  try {
1214
+ const limit = normalizeLimit(opts.limit);
1064
1215
  const client = new AtollClient();
1065
1216
  const orgId = await resolveOrgId2(client);
1066
1217
  const data = await client.get(`/api/webhooks?orgId=${orgId}`);
1067
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
1068
- for (const wh of data.webhooks) {
1069
- process.stdout.write(JSON.stringify(wh) + "\n");
1070
- }
1218
+ const allWebhooks = data.webhooks ?? [];
1219
+ const webhooks = allWebhooks.slice(0, limit);
1220
+ const truncated = allWebhooks.length > webhooks.length;
1221
+ if (isJsonMode()) {
1222
+ outputJson({
1223
+ resource: "webhooks",
1224
+ items: webhooks,
1225
+ total: allWebhooks.length,
1226
+ limit,
1227
+ offset: 0,
1228
+ nextOffset: null,
1229
+ truncated,
1230
+ hint: truncated ? "Increase --limit up to 100 to show more webhooks." : null
1231
+ });
1071
1232
  return;
1072
1233
  }
1073
- if (data.webhooks.length === 0) {
1234
+ if (webhooks.length === 0) {
1074
1235
  console.log("No webhooks configured.");
1075
1236
  return;
1076
1237
  }
1077
1238
  const header = bold(`${padEnd2("ID", 38)} ${padEnd2("URL", 40)} ${padEnd2("EVENTS", 20)} ACTIVE`);
1078
1239
  console.log(header);
1079
1240
  console.log("\u2500".repeat(100));
1080
- for (const wh of data.webhooks) {
1241
+ for (const wh of webhooks) {
1081
1242
  const evts = wh.events.length === 0 ? "all" : wh.events.join(",");
1082
1243
  const active = wh.enabled ? green("yes") : gray("no");
1083
1244
  const url = wh.url.length > 38 ? wh.url.slice(0, 35) + "\u2026" : wh.url;
1084
1245
  const evtStr = evts.length > 18 ? evts.slice(0, 15) + "\u2026" : evts;
1085
1246
  console.log(`${padEnd2(wh.id.slice(0, 36), 38)} ${padEnd2(url, 40)} ${padEnd2(evtStr, 20)} ${active}`);
1086
1247
  }
1087
- console.log(dim(`${data.webhooks.length} webhook(s)`));
1248
+ console.log(dim(`${webhooks.length} of ${allWebhooks.length} webhook(s)`));
1249
+ if (truncated) console.log(dim(`${allWebhooks.length - webhooks.length} more webhook(s) hidden; increase --limit to show more.`));
1088
1250
  } catch (err) {
1089
1251
  handleApiError2(err);
1090
1252
  }
@@ -1115,9 +1277,20 @@ webhookCommand.command("create").description("Create a new webhook").requiredOpt
1115
1277
  handleApiError2(err);
1116
1278
  }
1117
1279
  });
1118
- webhookCommand.command("delete <id>").description("Delete a webhook").action(async (id) => {
1280
+ webhookCommand.command("delete <id>").description("Delete a webhook").option("--force", "Delete the webhook").option("--dry-run", "Show what would be deleted without changing anything").action(async (id, opts) => {
1119
1281
  try {
1282
+ if (!opts.dryRun && !opts.force) {
1283
+ outputError("Webhook deletion requires --force.");
1284
+ process.exit(2);
1285
+ }
1120
1286
  const client = new AtollClient();
1287
+ if (opts.dryRun) {
1288
+ output(
1289
+ { dryRun: true, wouldDelete: { type: "webhook", id } },
1290
+ `Would delete webhook ${id}`
1291
+ );
1292
+ return;
1293
+ }
1121
1294
  await client.delete(`/api/webhooks/${id}`);
1122
1295
  output(
1123
1296
  { success: true, id },
@@ -1128,24 +1301,405 @@ webhookCommand.command("delete <id>").description("Delete a webhook").action(asy
1128
1301
  }
1129
1302
  });
1130
1303
 
1304
+ // src/commands/heartbeat.ts
1305
+ var import_commander8 = require("commander");
1306
+ var VALID_SEVERITIES = ["critical", "warning", "info"];
1307
+ var heartbeatCommand = new import_commander8.Command("heartbeat").description("Get the current agent heartbeat briefing").option("--signals-only", "Only show heartbeat signals").option("--severity <severity>", `Filter signals by severity (${VALID_SEVERITIES.join(", ")})`).action(async (opts) => {
1308
+ try {
1309
+ if (opts.severity && !VALID_SEVERITIES.includes(opts.severity)) {
1310
+ outputError(`Invalid severity "${opts.severity}". Must be one of: ${VALID_SEVERITIES.join(", ")}`);
1311
+ process.exit(2);
1312
+ }
1313
+ const client = new AtollClient();
1314
+ const org = await resolveOrg(client);
1315
+ const context = await client.get(`/api/orgs/${org.id}/heartbeat`);
1316
+ const signals = filterSignals(context.signals ?? [], opts.severity);
1317
+ if (isJsonMode()) {
1318
+ if (opts.signalsOnly) {
1319
+ outputJson({ signals });
1320
+ return;
1321
+ }
1322
+ outputJson({ ...context, signals });
1323
+ return;
1324
+ }
1325
+ renderHeartbeat(context, signals, opts.signalsOnly ?? false);
1326
+ } catch (err) {
1327
+ handleApiError(err);
1328
+ }
1329
+ });
1330
+ function filterSignals(signals, severity) {
1331
+ if (!severity) return signals;
1332
+ return signals.filter((signal) => signal.severity === severity);
1333
+ }
1334
+ function renderHeartbeat(context, signals, signalsOnly) {
1335
+ const agentName = context.agent?.display_name || context.agent?.id || "agent";
1336
+ console.log(bold(`Heartbeat for ${agentName}`));
1337
+ if (context.timestamp) console.log(dim(context.timestamp));
1338
+ console.log("");
1339
+ renderSignals(signals);
1340
+ if (signalsOnly) return;
1341
+ const assigned = context.assigned_issues ?? [];
1342
+ if (assigned.length > 0) {
1343
+ console.log("");
1344
+ console.log(bold("Assigned"));
1345
+ for (const issue of assigned.slice(0, 10)) {
1346
+ const id = issue.number == null ? issue.id.slice(0, 8) : `ATOLL-${issue.number}`;
1347
+ console.log(` ${pad(id, 10)} ${pad(issue.status, 13)} P${issue.priority} ${issue.title}`);
1348
+ }
1349
+ if (assigned.length > 10) console.log(dim(` ${assigned.length - 10} more assigned issue(s) hidden`));
1350
+ }
1351
+ const goals = context.goals ?? [];
1352
+ if (goals.length > 0) {
1353
+ console.log("");
1354
+ console.log(bold("Goals"));
1355
+ for (const goal of goals.slice(0, 5)) {
1356
+ const title = goal.goal?.title ?? "(untitled goal)";
1357
+ const days = goal.days_remaining == null ? "no deadline" : `${goal.days_remaining} days left`;
1358
+ const kpis = goal.kpis ?? [];
1359
+ const initiatives = goal.initiatives ?? [];
1360
+ const offPace = kpis.filter((kpi) => kpi.is_off_pace).length;
1361
+ const stale = kpis.filter((kpi) => kpi.is_stale).length;
1362
+ const stalledOrBlocked = initiatives.filter(
1363
+ (initiative) => (initiative.stalled_issues ?? 0) > 0 || (initiative.blocked_issues ?? 0) > 0
1364
+ ).length;
1365
+ console.log(` ${title} ${dim(days)}`);
1366
+ console.log(dim(` KPIs: ${offPace} off pace, ${stale} stale`));
1367
+ console.log(dim(` Initiatives: ${stalledOrBlocked} stalled/blocked`));
1368
+ }
1369
+ if (goals.length > 5) console.log(dim(` ${goals.length - 5} more goal(s) hidden`));
1370
+ }
1371
+ }
1372
+ function renderSignals(signals) {
1373
+ if (signals.length === 0) {
1374
+ console.log(gray("No heartbeat signals."));
1375
+ return;
1376
+ }
1377
+ const groups = [
1378
+ ["critical", "Critical"],
1379
+ ["warning", "Warnings"],
1380
+ ["info", "Info"]
1381
+ ];
1382
+ for (const [severity, label] of groups) {
1383
+ const group = signals.filter((signal) => signal.severity === severity);
1384
+ if (group.length === 0) continue;
1385
+ console.log(formatSeverityHeading(severity, label));
1386
+ for (const signal of group) {
1387
+ console.log(` ${pad(signal.type, 18)} ${signal.message}`);
1388
+ }
1389
+ if (severity !== "info") console.log("");
1390
+ }
1391
+ }
1392
+ function formatSeverityHeading(severity, label) {
1393
+ if (severity === "critical") return red(bold(label));
1394
+ if (severity === "warning") return yellow(bold(label));
1395
+ return cyan(bold(label));
1396
+ }
1397
+ function pad(value, length) {
1398
+ return value.length >= length ? value.slice(0, length) : value + " ".repeat(length - value.length);
1399
+ }
1400
+
1401
+ // src/commands/agent-context.ts
1402
+ var import_node_fs2 = require("fs");
1403
+ var import_node_path2 = require("path");
1404
+ var import_commander9 = require("commander");
1405
+ init_config();
1406
+ var ISSUE_STATUSES = ["backlog", "todo", "in_progress", "done", "cancelled"];
1407
+ var PRIORITIES = [0, 1, 2, 3];
1408
+ var SEVERITIES = ["critical", "warning", "info"];
1409
+ var SIGNAL_TYPES = [
1410
+ "kpi_off_pace",
1411
+ "kpi_stale",
1412
+ "issue_stale",
1413
+ "issue_blocked",
1414
+ "milestone_overdue",
1415
+ "initiative_stalled",
1416
+ "webhook_failing"
1417
+ ];
1418
+ var agentContextCommand = new import_commander9.Command("agent-context").description("Output machine-readable CLI context for agents").action(() => {
1419
+ const config = readConfig();
1420
+ const resolved = resolveConfig();
1421
+ const profiles = Object.entries(config.profiles ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([name, profile]) => ({
1422
+ name,
1423
+ active: name === config.activeProfile,
1424
+ apiKeySet: Boolean(profile.apiKey),
1425
+ orgSlug: profile.orgSlug ?? null,
1426
+ defaultTeam: profile.defaultTeam ?? null,
1427
+ baseUrl: profile.baseUrl ?? null
1428
+ }));
1429
+ outputJson({
1430
+ schema_version: "1",
1431
+ cli: {
1432
+ name: "atoll",
1433
+ json_flag: "--json",
1434
+ output: {
1435
+ stdout: "data",
1436
+ stderr: "diagnostics_and_errors",
1437
+ list_shape: "{ resource, items, total, limit, offset, nextOffset, truncated, hint }"
1438
+ }
1439
+ },
1440
+ selected_context: {
1441
+ activeProfile: config.activeProfile ?? null,
1442
+ selectedProfile: resolved.profile ?? null,
1443
+ orgSlug: resolved.orgSlug ?? null,
1444
+ defaultTeam: resolved.defaultTeam ?? null,
1445
+ baseUrl: resolved.baseUrl ?? null,
1446
+ apiKeySet: Boolean(resolved.apiKey)
1447
+ },
1448
+ available_profiles: profiles,
1449
+ commands: buildCommandContext(),
1450
+ skill_manifests: findSkillManifests()
1451
+ });
1452
+ });
1453
+ var skillPathCommand = new import_commander9.Command("skill-path").description("Output local Atoll skill manifest paths when available").action(() => {
1454
+ outputJson({ skill_manifests: findSkillManifests() });
1455
+ });
1456
+ function buildCommandContext() {
1457
+ return {
1458
+ heartbeat: {
1459
+ description: "Primary agent orientation command. Reads current goals, KPI pace, initiative health, assigned work, and prioritized signals.",
1460
+ subcommands: {},
1461
+ flags: {
1462
+ "--signals-only": { type: "boolean", default: false },
1463
+ "--severity": { type: "enum", values: SEVERITIES },
1464
+ "--json": { type: "boolean", default: false }
1465
+ },
1466
+ signal_types: SIGNAL_TYPES
1467
+ },
1468
+ issue: {
1469
+ subcommands: {
1470
+ list: {
1471
+ flags: {
1472
+ "--status": { type: "enum", values: ISSUE_STATUSES },
1473
+ "--assignee": { type: "string" },
1474
+ "--priority": { type: "enum", values: PRIORITIES },
1475
+ "--limit": { type: "integer", min: 1, max: 100, default: 25 },
1476
+ "--offset": { type: "integer", min: 0, default: 0 },
1477
+ "--json": { type: "boolean", default: false }
1478
+ }
1479
+ },
1480
+ get: { alias_for: "view", args: ["identifier"], flags: { "--json": { type: "boolean", default: false } } },
1481
+ view: { args: ["identifier"], flags: { "--json": { type: "boolean", default: false } } },
1482
+ create: {
1483
+ flags: {
1484
+ "--title": { type: "string", required: true },
1485
+ "--description": { type: "string" },
1486
+ "--status": { type: "enum", values: ISSUE_STATUSES },
1487
+ "--priority": { type: "enum", values: PRIORITIES },
1488
+ "--json": { type: "boolean", default: false }
1489
+ }
1490
+ },
1491
+ update: {
1492
+ args: ["identifier"],
1493
+ flags: {
1494
+ "--title": { type: "string" },
1495
+ "--status": { type: "enum", values: ISSUE_STATUSES },
1496
+ "--priority": { type: "enum", values: PRIORITIES },
1497
+ "--json": { type: "boolean", default: false }
1498
+ }
1499
+ },
1500
+ archive: { args: ["identifier"], flags: { "--dry-run": { type: "boolean" }, "--json": { type: "boolean" } } },
1501
+ unarchive: { args: ["identifier"], flags: { "--dry-run": { type: "boolean" }, "--json": { type: "boolean" } } },
1502
+ delete: {
1503
+ args: ["identifier"],
1504
+ flags: {
1505
+ "--force": { type: "boolean", required: true },
1506
+ "--dry-run": { type: "boolean" },
1507
+ "--json": { type: "boolean", default: false }
1508
+ }
1509
+ },
1510
+ assign: { args: ["identifier"], flags: { "--to": { type: "string", required: true }, "--json": { type: "boolean" } } },
1511
+ unassign: { args: ["identifier"], flags: { "--json": { type: "boolean" } } }
1512
+ }
1513
+ },
1514
+ project: {
1515
+ subcommands: {
1516
+ list: { flags: { "--limit": { type: "integer", min: 1, max: 100, default: 25 }, "--json": { type: "boolean" } } },
1517
+ get: { alias_for: "view", args: ["projectId"], flags: { "--json": { type: "boolean" } } },
1518
+ view: { args: ["projectId"], flags: { "--json": { type: "boolean" } } },
1519
+ create: {
1520
+ flags: {
1521
+ "--name": { type: "string", required: true },
1522
+ "--description": { type: "string" },
1523
+ "--color": { type: "string", default: "#6366f1" },
1524
+ "--icon": { type: "string", default: "folder" },
1525
+ "--json": { type: "boolean" }
1526
+ }
1527
+ }
1528
+ }
1529
+ },
1530
+ comment: {
1531
+ subcommands: {
1532
+ list: { args: ["identifier"], flags: { "--limit": { type: "integer", min: 1, max: 100, default: 25 }, "--json": { type: "boolean" } } },
1533
+ add: { args: ["identifier"], flags: { "--body": { type: "string", required: true }, "--json": { type: "boolean" } } },
1534
+ update: {
1535
+ args: ["comment-id"],
1536
+ flags: { "--issue": { type: "string", required: true }, "--body": { type: "string", required: true }, "--json": { type: "boolean" } }
1537
+ },
1538
+ delete: {
1539
+ args: ["comment-id"],
1540
+ flags: {
1541
+ "--issue": { type: "string", required: true },
1542
+ "--force": { type: "boolean", required: true },
1543
+ "--dry-run": { type: "boolean" },
1544
+ "--json": { type: "boolean" }
1545
+ }
1546
+ }
1547
+ }
1548
+ },
1549
+ feedback: {
1550
+ description: "Record CLI or platform friction locally and optionally submit it upstream.",
1551
+ subcommands: {
1552
+ add: {
1553
+ alias: "feedback <text>",
1554
+ flags: {
1555
+ "--type": { type: "enum", values: ["bug", "feature"], default: "bug" },
1556
+ "--url": { type: "string" },
1557
+ "--send": { type: "boolean", default: false },
1558
+ "--json": { type: "boolean" }
1559
+ }
1560
+ },
1561
+ list: { flags: { "--limit": { type: "integer", min: 1, max: 100, default: 25 }, "--json": { type: "boolean" } } }
1562
+ }
1563
+ }
1564
+ };
1565
+ }
1566
+ function findSkillManifests() {
1567
+ const repoRoot = (0, import_node_path2.resolve)(__dirname, "..", "..", "..");
1568
+ const candidates = [
1569
+ ["codex", (0, import_node_path2.join)(repoRoot, "packages", "skill-codex", "skill", "SKILL.md")],
1570
+ ["claude", (0, import_node_path2.join)(repoRoot, "packages", "skill-claude", "skill", "SKILL.md")],
1571
+ ["gemini", (0, import_node_path2.join)(repoRoot, "packages", "skill-gemini", "skill", "SKILL.md")],
1572
+ ["clawhub", (0, import_node_path2.join)(repoRoot, "clawhub", "atoll-api", "SKILL.md")]
1573
+ ];
1574
+ return candidates.map(([name, path]) => ({ name, path, exists: (0, import_node_fs2.existsSync)(path) }));
1575
+ }
1576
+
1577
+ // src/commands/feedback.ts
1578
+ var import_node_fs3 = require("fs");
1579
+ var import_node_os2 = require("os");
1580
+ var import_node_path3 = require("path");
1581
+ var import_commander10 = require("commander");
1582
+ init_config();
1583
+ var CONFIG_DIR2 = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".atoll");
1584
+ var FEEDBACK_PATH = (0, import_node_path3.join)(CONFIG_DIR2, "feedback.jsonl");
1585
+ var VALID_TYPES = ["bug", "feature"];
1586
+ var DEFAULT_BASE_URL2 = "https://atollhq.com";
1587
+ var feedbackCommand = new import_commander10.Command("feedback").description("Record CLI or platform feedback").argument("[text...]", "Feedback text").option("--type <type>", `Feedback type (${VALID_TYPES.join(", ")})`, "bug").option("--url <url>", "Related page or endpoint URL").option("--send", "Also submit to the configured Atoll feedback endpoint").action(async (text, opts) => {
1588
+ if (text.length === 0) {
1589
+ outputError('Feedback text is required. Usage: atoll feedback "what went wrong"');
1590
+ process.exit(2);
1591
+ }
1592
+ await recordFeedback(text.join(" "), opts);
1593
+ });
1594
+ feedbackCommand.command("add <text...>").description("Record feedback").option("--type <type>", `Feedback type (${VALID_TYPES.join(", ")})`, "bug").option("--url <url>", "Related page or endpoint URL").option("--send", "Also submit to the configured Atoll feedback endpoint").action(async (text, opts) => {
1595
+ await recordFeedback(text.join(" "), opts);
1596
+ });
1597
+ feedbackCommand.command("list").description("List locally recorded feedback").option("--limit <n>", "Max results (1-100)", parseInt).action((opts) => {
1598
+ const limit = normalizeLimit(opts.limit);
1599
+ const entries = readFeedbackEntries().slice(-limit).reverse();
1600
+ if (isJsonMode()) {
1601
+ outputJson({
1602
+ resource: "feedback",
1603
+ items: entries,
1604
+ total: readFeedbackEntries().length,
1605
+ limit,
1606
+ offset: 0,
1607
+ nextOffset: null,
1608
+ truncated: readFeedbackEntries().length > entries.length,
1609
+ hint: null
1610
+ });
1611
+ return;
1612
+ }
1613
+ if (entries.length === 0) {
1614
+ console.log("No local feedback recorded.");
1615
+ return;
1616
+ }
1617
+ for (const entry of entries) {
1618
+ const sent = entry.sent_upstream ? `sent:${entry.upstream_status}` : "local";
1619
+ console.log(`${entry.created_at} ${entry.type} ${sent}`);
1620
+ console.log(` ${entry.description}`);
1621
+ if (entry.url) console.log(dim(` ${entry.url}`));
1622
+ }
1623
+ });
1624
+ async function recordFeedback(description, opts) {
1625
+ if (!VALID_TYPES.includes(opts.type)) {
1626
+ outputError(`Invalid feedback type "${opts.type}". Must be one of: ${VALID_TYPES.join(", ")}`);
1627
+ process.exit(2);
1628
+ }
1629
+ const entry = {
1630
+ id: makeId(),
1631
+ type: opts.type,
1632
+ description,
1633
+ url: opts.url ?? null,
1634
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1635
+ sent_upstream: false,
1636
+ upstream_status: null,
1637
+ upstream_error: null
1638
+ };
1639
+ const endpoint = process.env.ATOLL_FEEDBACK_ENDPOINT || (opts.send ? `${resolveConfig().baseUrl || DEFAULT_BASE_URL2}/api/feedback` : null);
1640
+ if (endpoint) {
1641
+ try {
1642
+ const response = await fetch(endpoint, {
1643
+ method: "POST",
1644
+ headers: { "Content-Type": "application/json" },
1645
+ body: JSON.stringify({
1646
+ type: entry.type,
1647
+ description: entry.description,
1648
+ url: entry.url ?? void 0,
1649
+ userName: getReporterName()
1650
+ })
1651
+ });
1652
+ entry.sent_upstream = response.ok;
1653
+ entry.upstream_status = response.status;
1654
+ if (!response.ok) entry.upstream_error = await response.text();
1655
+ } catch (err) {
1656
+ entry.upstream_error = err.message ?? String(err);
1657
+ }
1658
+ }
1659
+ appendFeedbackEntry(entry);
1660
+ output(
1661
+ { feedback: entry },
1662
+ entry.sent_upstream ? success(`Feedback recorded locally and sent upstream (${entry.upstream_status})`) : success("Feedback recorded locally")
1663
+ );
1664
+ }
1665
+ function readFeedbackEntries() {
1666
+ if (!(0, import_node_fs3.existsSync)(FEEDBACK_PATH)) return [];
1667
+ return (0, import_node_fs3.readFileSync)(FEEDBACK_PATH, "utf-8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
1668
+ }
1669
+ function appendFeedbackEntry(entry) {
1670
+ if (!(0, import_node_fs3.existsSync)(CONFIG_DIR2)) (0, import_node_fs3.mkdirSync)(CONFIG_DIR2, { recursive: true });
1671
+ (0, import_node_fs3.appendFileSync)(FEEDBACK_PATH, JSON.stringify(entry) + "\n", "utf-8");
1672
+ }
1673
+ function getReporterName() {
1674
+ const config = readConfig();
1675
+ return process.env.ATOLL_PROFILE || config.activeProfile || void 0;
1676
+ }
1677
+ function makeId() {
1678
+ return `fb_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
1679
+ }
1680
+
1131
1681
  // src/index.ts
1132
1682
  function readCliVersion() {
1133
1683
  try {
1134
- const packageJson = JSON.parse((0, import_node_fs2.readFileSync)((0, import_node_path2.join)(__dirname, "..", "package.json"), "utf-8"));
1684
+ const packageJson = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path4.join)(__dirname, "..", "package.json"), "utf-8"));
1135
1685
  return packageJson.version ?? "0.0.0";
1136
1686
  } catch {
1137
1687
  return "0.0.0";
1138
1688
  }
1139
1689
  }
1140
- var program = new import_commander8.Command("atoll").description("Atoll CLI \u2014 project management from the terminal").version(readCliVersion()).addHelpText("after", `
1690
+ var program = new import_commander11.Command("atoll").description("Atoll CLI \u2014 project management from the terminal").version(readCliVersion()).addHelpText("after", `
1141
1691
  Examples:
1142
1692
  $ atoll issue list
1143
1693
  $ atoll issue create --title "Fix login bug" --priority 1
1144
1694
  $ atoll issue view ATOLL-42
1695
+ $ atoll heartbeat
1145
1696
  $ atoll project list
1146
1697
  $ atoll milestone list --project <id>
1147
- $ atoll config show`).option("--profile <name>", "Use a saved auth profile (env: ATOLL_PROFILE)").option("--org <slug>", "Override default org slug (env: ATOLL_ORG)").option("--team <id>", "Override default team slug/ID (env: ATOLL_TEAM)").hook("preAction", (thisCommand) => {
1148
- const opts = thisCommand.opts();
1698
+ $ atoll config show`).option("--profile <name>", "Use a saved auth profile (env: ATOLL_PROFILE)").option("--org <slug>", "Override default org slug (env: ATOLL_ORG)").option("--team <id>", "Override default team slug/ID (env: ATOLL_TEAM)").option("--json", "Emit machine-readable JSON").hook("preAction", (_thisCommand, actionCommand) => {
1699
+ const opts = actionCommand.optsWithGlobals();
1700
+ if (opts.json) {
1701
+ enableJsonMode();
1702
+ }
1149
1703
  if (opts.profile) {
1150
1704
  process.env.ATOLL_PROFILE = opts.profile;
1151
1705
  }
@@ -1165,7 +1719,11 @@ program.addCommand(projectCommand);
1165
1719
  program.addCommand(milestoneCommand);
1166
1720
  program.addCommand(configCommand);
1167
1721
  program.addCommand(webhookCommand);
1168
- var completionCommand = new import_commander8.Command("completion").description("Output shell completion scripts").addHelpText("after", `
1722
+ program.addCommand(heartbeatCommand);
1723
+ program.addCommand(agentContextCommand);
1724
+ program.addCommand(skillPathCommand);
1725
+ program.addCommand(feedbackCommand);
1726
+ var completionCommand = new import_commander11.Command("completion").description("Output shell completion scripts").addHelpText("after", `
1169
1727
  Examples:
1170
1728
  $ atoll completion bash >> ~/.bashrc
1171
1729
  $ atoll completion zsh >> ~/.zshrc`);
@@ -1176,6 +1734,7 @@ completionCommand.command("zsh").description("Output zsh completion script").act
1176
1734
  process.stdout.write(zshCompletion());
1177
1735
  });
1178
1736
  program.addCommand(completionCommand);
1737
+ addJsonOptionToSubcommands(program);
1179
1738
  program.parse();
1180
1739
  function bashCompletion() {
1181
1740
  return `# Atoll CLI bash completion
@@ -1185,13 +1744,14 @@ _atoll_completions() {
1185
1744
  local cur prev words cword
1186
1745
  _init_completion || return
1187
1746
 
1188
- local commands="auth issue comment project milestone config webhook completion"
1189
- local issue_cmds="list view create update delete assign unassign"
1190
- local project_cmds="list create view"
1747
+ local commands="auth issue comment project milestone config webhook heartbeat agent-context skill-path feedback completion"
1748
+ local issue_cmds="list view get create update archive unarchive delete assign unassign"
1749
+ local project_cmds="list create view get"
1191
1750
  local milestone_cmds="list create"
1192
1751
  local config_cmds="show set-org set-team set-base-url"
1193
1752
  local auth_cmds="login logout status profiles use"
1194
1753
  local webhook_cmds="list create delete"
1754
+ local feedback_cmds="add list"
1195
1755
  local completion_cmds="bash zsh"
1196
1756
 
1197
1757
  if [ "\${COMP_CWORD}" -eq 1 ]; then
@@ -1230,6 +1790,11 @@ _atoll_completions() {
1230
1790
  COMPREPLY=( $(compgen -W "\${webhook_cmds}" -- "\${cur}") )
1231
1791
  fi
1232
1792
  ;;
1793
+ feedback)
1794
+ if [ "\${COMP_CWORD}" -eq 2 ]; then
1795
+ COMPREPLY=( $(compgen -W "\${feedback_cmds}" -- "\${cur}") )
1796
+ fi
1797
+ ;;
1233
1798
  completion)
1234
1799
  if [ "\${COMP_CWORD}" -eq 2 ]; then
1235
1800
  COMPREPLY=( $(compgen -W "\${completion_cmds}" -- "\${cur}") )
@@ -1263,6 +1828,10 @@ _atoll() {
1263
1828
  'milestone[Manage milestones]' \\
1264
1829
  'config[Manage CLI configuration]' \\
1265
1830
  'webhook[Manage outbound webhooks]' \\
1831
+ 'heartbeat[Get agent heartbeat briefing]' \\
1832
+ 'agent-context[Output machine-readable CLI context]' \\
1833
+ 'skill-path[Output local Atoll skill manifest paths]' \\
1834
+ 'feedback[Record CLI or platform feedback]' \\
1266
1835
  'completion[Output shell completion scripts]'
1267
1836
  ;;
1268
1837
  args)
@@ -1271,7 +1840,10 @@ _atoll() {
1271
1840
  _values 'subcommand' \\
1272
1841
  'list[List issues]' \\
1273
1842
  'view[View issue details]' \\
1843
+ 'get[Get issue details]' \\
1274
1844
  'create[Create a new issue]' \\
1845
+ 'archive[Archive an issue]' \\
1846
+ 'unarchive[Unarchive an issue]' \\
1275
1847
  'update[Update an issue]' \\
1276
1848
  'delete[Delete an issue]' \\
1277
1849
  'assign[Assign an issue]' \\
@@ -1281,7 +1853,8 @@ _atoll() {
1281
1853
  _values 'subcommand' \\
1282
1854
  'list[List projects]' \\
1283
1855
  'create[Create a project]' \\
1284
- 'view[View project details]'
1856
+ 'view[View project details]' \\
1857
+ 'get[Get project details]'
1285
1858
  ;;
1286
1859
  milestone)
1287
1860
  _values 'subcommand' \\
@@ -1309,6 +1882,11 @@ _atoll() {
1309
1882
  'create[Create a webhook]' \\
1310
1883
  'delete[Delete a webhook]'
1311
1884
  ;;
1885
+ feedback)
1886
+ _values 'subcommand' \\
1887
+ 'add[Record feedback]' \\
1888
+ 'list[List local feedback]'
1889
+ ;;
1312
1890
  completion)
1313
1891
  _values 'shell' 'bash' 'zsh'
1314
1892
  ;;
@@ -1320,4 +1898,12 @@ _atoll() {
1320
1898
  _atoll "$@"
1321
1899
  `;
1322
1900
  }
1901
+ function addJsonOptionToSubcommands(command) {
1902
+ for (const subcommand of command.commands) {
1903
+ if (!subcommand.options.some((option) => option.long === "--json")) {
1904
+ subcommand.option("--json", "Emit machine-readable JSON");
1905
+ }
1906
+ addJsonOptionToSubcommands(subcommand);
1907
+ }
1908
+ }
1323
1909
  //# sourceMappingURL=index.js.map