@atollhq/cli 0.1.2 → 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,7 +81,9 @@ var init_config = __esm({
81
81
  });
82
82
 
83
83
  // src/index.ts
84
- var import_commander8 = require("commander");
84
+ var import_commander11 = require("commander");
85
+ var import_node_fs4 = require("fs");
86
+ var import_node_path4 = require("path");
85
87
 
86
88
  // src/commands/auth.ts
87
89
  var import_commander = require("commander");
@@ -95,6 +97,9 @@ function isJsonMode() {
95
97
  if (process.env.OUTPUT_FORMAT === "json") return true;
96
98
  return !process.stdout.isTTY;
97
99
  }
100
+ function enableJsonMode() {
101
+ process.env.OUTPUT_FORMAT = "json";
102
+ }
98
103
  function output(data, humanReadable) {
99
104
  if (isJsonMode()) {
100
105
  process.stdout.write(JSON.stringify(data) + "\n");
@@ -102,13 +107,34 @@ function output(data, humanReadable) {
102
107
  console.log(humanReadable);
103
108
  }
104
109
  }
110
+ function outputJson(data) {
111
+ process.stdout.write(JSON.stringify(data) + "\n");
112
+ }
105
113
  function outputError(message) {
106
114
  if (isJsonMode()) {
107
- process.stdout.write(JSON.stringify({ error: message }) + "\n");
115
+ process.stderr.write(JSON.stringify({ error: message }) + "\n");
108
116
  } else {
109
117
  console.error(`Error: ${message}`);
110
118
  }
111
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
+ }
112
138
 
113
139
  // src/lib/client.ts
114
140
  var DEFAULT_BASE_URL = "https://atollhq.com";
@@ -138,7 +164,10 @@ var AtollClient = class {
138
164
  const body = await res.text();
139
165
  throw new Error(`API ${res.status}: ${body}`);
140
166
  }
141
- 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);
142
171
  }
143
172
  async get(path) {
144
173
  return this.request(path, { method: "GET" });
@@ -462,15 +491,26 @@ Examples:
462
491
  $ atoll issue create --title "Fix login" --priority 1 --status todo
463
492
  $ atoll issue update ATOLL-42 --status done
464
493
  $ atoll issue assign ATOLL-42 --to <user-id>`);
465
- 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) => {
466
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
+ }
467
504
  const client = new AtollClient();
468
505
  const org = await resolveOrg(client);
506
+ const limit = normalizeLimit(opts.limit);
507
+ const offset = normalizeOffset(opts.offset);
469
508
  const params = new URLSearchParams();
470
509
  if (opts.status) params.set("status", opts.status);
471
510
  if (opts.assignee) params.set("assigneeId", opts.assignee);
472
511
  if (opts.priority !== void 0) params.set("priority", String(opts.priority));
473
- 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));
474
514
  const qs = params.toString();
475
515
  const [data, projects] = await Promise.all([
476
516
  client.get(`/api/orgs/${org.id}/issues${qs ? `?${qs}` : ""}`),
@@ -479,10 +519,17 @@ issueCommand.command("list").description("List issues").option("--status <status
479
519
  const enriched = data.issues.map(
480
520
  (issue) => attachIssueUrl(issue, client.baseUrl, org.slug, projects)
481
521
  );
482
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
483
- for (const issue of enriched) {
484
- process.stdout.write(JSON.stringify(issue) + "\n");
485
- }
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
+ });
486
533
  return;
487
534
  }
488
535
  if (enriched.length === 0) {
@@ -500,11 +547,14 @@ issueCommand.command("list").description("List issues").option("--status <status
500
547
  console.log(`${padEnd(id, 10)} ${statusColor(issue.status, padEnd(issue.status, 14))} ${icon} ${padEnd(pri, 8)} ${title}`);
501
548
  }
502
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
+ }
503
553
  } catch (err) {
504
554
  handleApiError(err);
505
555
  }
506
556
  });
507
- issueCommand.command("view <identifier>").description("View issue details (UUID or PREFIX-NUMBER e.g. ATOLL-42)").action(async (identifier) => {
557
+ async function viewIssue(identifier) {
508
558
  try {
509
559
  const client = new AtollClient();
510
560
  const org = await resolveOrg(client);
@@ -514,8 +564,8 @@ issueCommand.command("view <identifier>").description("View issue details (UUID
514
564
  fetchProjectMap(client, org.id)
515
565
  ]);
516
566
  const enriched = attachIssueUrl(issue, client.baseUrl, org.slug, projects);
517
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
518
- process.stdout.write(JSON.stringify(enriched) + "\n");
567
+ if (isJsonMode()) {
568
+ outputJson(enriched);
519
569
  return;
520
570
  }
521
571
  const id = formatIdentifier(enriched, projects);
@@ -542,7 +592,9 @@ Subtasks: ${enriched.sub_tasks.length}`);
542
592
  } catch (err) {
543
593
  handleApiError(err);
544
594
  }
545
- });
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);
546
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) => {
547
599
  try {
548
600
  if (opts.status && !VALID_STATUSES.includes(opts.status)) {
@@ -603,11 +655,22 @@ issueCommand.command("update <identifier>").description("Update an issue").optio
603
655
  handleApiError(err);
604
656
  }
605
657
  });
606
- 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) => {
607
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
+ }
608
664
  const client = new AtollClient();
609
665
  const orgId = await resolveOrgId(client);
610
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
+ }
611
674
  await client.delete(`/api/orgs/${orgId}/issues/${issueId}`);
612
675
  output(
613
676
  { success: true, id: issueId },
@@ -617,6 +680,48 @@ issueCommand.command("delete <identifier>").description("Delete an issue (admin/
617
680
  handleApiError(err);
618
681
  }
619
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
+ });
620
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) => {
621
726
  try {
622
727
  const client = new AtollClient();
@@ -691,32 +796,44 @@ function formatTimestamp(ts) {
691
796
  return d.toLocaleString();
692
797
  }
693
798
  var commentCommand = new import_commander3.Command("comment").description("Manage issue comments");
694
- 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) => {
695
800
  try {
801
+ const limit = normalizeLimit(opts.limit);
696
802
  const client = new AtollClient();
697
803
  const orgId = await resolveOrgId(client);
698
804
  const issueId = await resolveIssueId(client, orgId, identifier);
699
805
  const data = await client.get(
700
806
  `/api/orgs/${orgId}/issues/${issueId}/comments`
701
807
  );
702
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
703
- for (const comment of data.comments) {
704
- process.stdout.write(JSON.stringify(comment) + "\n");
705
- }
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
+ });
706
822
  return;
707
823
  }
708
- if (data.comments.length === 0) {
824
+ if (comments.length === 0) {
709
825
  console.log("No comments found.");
710
826
  return;
711
827
  }
712
- for (const comment of data.comments) {
828
+ for (const comment of comments) {
713
829
  const author = formatAuthor(comment);
714
830
  const time = formatTimestamp(comment.created_at);
715
831
  console.log(`${BOLD2}${CYAN}${author}${RESET2} ${DIM2}${time}${RESET2}`);
716
832
  console.log(comment.body);
717
833
  console.log();
718
834
  }
719
- 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}`);
720
837
  } catch (err) {
721
838
  handleApiError(err);
722
839
  }
@@ -755,11 +872,22 @@ commentCommand.command("update <comment-id>").description("Update a comment").re
755
872
  handleApiError(err);
756
873
  }
757
874
  });
758
- 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) => {
759
876
  try {
877
+ if (!opts.dryRun && !opts.force) {
878
+ outputError("Comment deletion requires --force.");
879
+ process.exit(2);
880
+ }
760
881
  const client = new AtollClient();
761
882
  const orgId = await resolveOrgId(client);
762
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
+ }
763
891
  await client.delete(`/api/orgs/${orgId}/issues/${issueId}/comments/${commentId}`);
764
892
  output(
765
893
  { success: true, id: commentId },
@@ -785,16 +913,26 @@ Examples:
785
913
  $ atoll project list
786
914
  $ atoll project create --name "My Project" --icon \u{1F680}
787
915
  $ atoll project view <project-id>`);
788
- 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) => {
789
917
  const client = new AtollClient();
790
918
  try {
919
+ const limit = normalizeLimit(opts.limit);
791
920
  const org = await resolveOrg(client);
792
921
  const data = await client.get(`/api/orgs/${org.id}/projects`);
793
- const projects = (data.projects ?? []).map((p) => attachProjectUrl(p, client.baseUrl, org.slug));
794
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
795
- for (const p of projects) {
796
- process.stdout.write(JSON.stringify(p) + "\n");
797
- }
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
+ });
798
936
  return;
799
937
  }
800
938
  if (projects.length === 0) {
@@ -811,6 +949,7 @@ projectCommand.command("list").description("List all projects with progress").ac
811
949
  if (p.status === "archived") console.log(` ${dim("[archived]")}`);
812
950
  console.log("");
813
951
  }
952
+ if (truncated) console.log(dim(`${allProjects.length - projects.length} more project(s) hidden; increase --limit to show more.`));
814
953
  } catch (err) {
815
954
  handleApiError(err);
816
955
  }
@@ -835,7 +974,7 @@ ${project.url}` : ""}`)
835
974
  handleApiError(err);
836
975
  }
837
976
  });
838
- projectCommand.command("view <projectId>").description("View project details and issues").action(async (projectId) => {
977
+ async function viewProject(projectId) {
839
978
  const client = new AtollClient();
840
979
  try {
841
980
  const org = await resolveOrg(client);
@@ -843,8 +982,8 @@ projectCommand.command("view <projectId>").description("View project details and
843
982
  `/api/orgs/${org.id}/projects/${projectId}`
844
983
  );
845
984
  const p = attachProjectUrl(data.project, client.baseUrl, org.slug);
846
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
847
- process.stdout.write(JSON.stringify(p) + "\n");
985
+ if (isJsonMode()) {
986
+ outputJson(p);
848
987
  return;
849
988
  }
850
989
  const progress = p.progress ?? 0;
@@ -867,7 +1006,9 @@ projectCommand.command("view <projectId>").description("View project details and
867
1006
  } catch (err) {
868
1007
  handleApiError(err);
869
1008
  }
870
- });
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);
871
1012
 
872
1013
  // src/commands/milestone.ts
873
1014
  var import_commander5 = require("commander");
@@ -880,18 +1021,28 @@ var milestoneCommand = new import_commander5.Command("milestone").description("M
880
1021
  Examples:
881
1022
  $ atoll milestone list --project <project-id>
882
1023
  $ atoll milestone create --project <project-id> --name "v1.0" --date 2026-06-01`);
883
- 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) => {
884
1025
  const client = new AtollClient();
885
1026
  try {
1027
+ const limit = normalizeLimit(opts.limit);
886
1028
  const orgId = await resolveOrgId(client);
887
1029
  const data = await client.get(
888
1030
  `/api/orgs/${orgId}/projects/${opts.project}/milestones`
889
1031
  );
890
- const milestones = data.milestones ?? [];
891
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
892
- for (const m of milestones) {
893
- process.stdout.write(JSON.stringify(m) + "\n");
894
- }
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
+ });
895
1046
  return;
896
1047
  }
897
1048
  if (milestones.length === 0) {
@@ -909,6 +1060,7 @@ milestoneCommand.command("list").description("List milestones for a project").re
909
1060
  if (m.status === "closed") console.log(` ${dim("[closed]")}`);
910
1061
  console.log("");
911
1062
  }
1063
+ if (truncated) console.log(dim(`${allMilestones.length - milestones.length} more milestone(s) hidden; increase --limit to show more.`));
912
1064
  } catch (err) {
913
1065
  handleApiError(err);
914
1066
  }
@@ -1057,32 +1209,44 @@ Examples:
1057
1209
  $ atoll webhook list
1058
1210
  $ atoll webhook create --url https://example.com/hook --events issue.created,issue.updated
1059
1211
  $ atoll webhook delete <id>`);
1060
- 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) => {
1061
1213
  try {
1214
+ const limit = normalizeLimit(opts.limit);
1062
1215
  const client = new AtollClient();
1063
1216
  const orgId = await resolveOrgId2(client);
1064
1217
  const data = await client.get(`/api/webhooks?orgId=${orgId}`);
1065
- if (!process.stdout.isTTY || process.env.OUTPUT_FORMAT === "json") {
1066
- for (const wh of data.webhooks) {
1067
- process.stdout.write(JSON.stringify(wh) + "\n");
1068
- }
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
+ });
1069
1232
  return;
1070
1233
  }
1071
- if (data.webhooks.length === 0) {
1234
+ if (webhooks.length === 0) {
1072
1235
  console.log("No webhooks configured.");
1073
1236
  return;
1074
1237
  }
1075
1238
  const header = bold(`${padEnd2("ID", 38)} ${padEnd2("URL", 40)} ${padEnd2("EVENTS", 20)} ACTIVE`);
1076
1239
  console.log(header);
1077
1240
  console.log("\u2500".repeat(100));
1078
- for (const wh of data.webhooks) {
1241
+ for (const wh of webhooks) {
1079
1242
  const evts = wh.events.length === 0 ? "all" : wh.events.join(",");
1080
1243
  const active = wh.enabled ? green("yes") : gray("no");
1081
1244
  const url = wh.url.length > 38 ? wh.url.slice(0, 35) + "\u2026" : wh.url;
1082
1245
  const evtStr = evts.length > 18 ? evts.slice(0, 15) + "\u2026" : evts;
1083
1246
  console.log(`${padEnd2(wh.id.slice(0, 36), 38)} ${padEnd2(url, 40)} ${padEnd2(evtStr, 20)} ${active}`);
1084
1247
  }
1085
- 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.`));
1086
1250
  } catch (err) {
1087
1251
  handleApiError2(err);
1088
1252
  }
@@ -1113,9 +1277,20 @@ webhookCommand.command("create").description("Create a new webhook").requiredOpt
1113
1277
  handleApiError2(err);
1114
1278
  }
1115
1279
  });
1116
- 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) => {
1117
1281
  try {
1282
+ if (!opts.dryRun && !opts.force) {
1283
+ outputError("Webhook deletion requires --force.");
1284
+ process.exit(2);
1285
+ }
1118
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
+ }
1119
1294
  await client.delete(`/api/webhooks/${id}`);
1120
1295
  output(
1121
1296
  { success: true, id },
@@ -1126,16 +1301,405 @@ webhookCommand.command("delete <id>").description("Delete a webhook").action(asy
1126
1301
  }
1127
1302
  });
1128
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
+
1129
1681
  // src/index.ts
1130
- var program = new import_commander8.Command("atoll").description("Atoll CLI \u2014 project management from the terminal").version("0.1.0").addHelpText("after", `
1682
+ function readCliVersion() {
1683
+ try {
1684
+ const packageJson = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path4.join)(__dirname, "..", "package.json"), "utf-8"));
1685
+ return packageJson.version ?? "0.0.0";
1686
+ } catch {
1687
+ return "0.0.0";
1688
+ }
1689
+ }
1690
+ var program = new import_commander11.Command("atoll").description("Atoll CLI \u2014 project management from the terminal").version(readCliVersion()).addHelpText("after", `
1131
1691
  Examples:
1132
1692
  $ atoll issue list
1133
1693
  $ atoll issue create --title "Fix login bug" --priority 1
1134
1694
  $ atoll issue view ATOLL-42
1695
+ $ atoll heartbeat
1135
1696
  $ atoll project list
1136
1697
  $ atoll milestone list --project <id>
1137
- $ 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) => {
1138
- 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
+ }
1139
1703
  if (opts.profile) {
1140
1704
  process.env.ATOLL_PROFILE = opts.profile;
1141
1705
  }
@@ -1155,7 +1719,11 @@ program.addCommand(projectCommand);
1155
1719
  program.addCommand(milestoneCommand);
1156
1720
  program.addCommand(configCommand);
1157
1721
  program.addCommand(webhookCommand);
1158
- 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", `
1159
1727
  Examples:
1160
1728
  $ atoll completion bash >> ~/.bashrc
1161
1729
  $ atoll completion zsh >> ~/.zshrc`);
@@ -1166,6 +1734,7 @@ completionCommand.command("zsh").description("Output zsh completion script").act
1166
1734
  process.stdout.write(zshCompletion());
1167
1735
  });
1168
1736
  program.addCommand(completionCommand);
1737
+ addJsonOptionToSubcommands(program);
1169
1738
  program.parse();
1170
1739
  function bashCompletion() {
1171
1740
  return `# Atoll CLI bash completion
@@ -1175,13 +1744,14 @@ _atoll_completions() {
1175
1744
  local cur prev words cword
1176
1745
  _init_completion || return
1177
1746
 
1178
- local commands="auth issue comment project milestone config webhook completion"
1179
- local issue_cmds="list view create update delete assign unassign"
1180
- 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"
1181
1750
  local milestone_cmds="list create"
1182
1751
  local config_cmds="show set-org set-team set-base-url"
1183
1752
  local auth_cmds="login logout status profiles use"
1184
1753
  local webhook_cmds="list create delete"
1754
+ local feedback_cmds="add list"
1185
1755
  local completion_cmds="bash zsh"
1186
1756
 
1187
1757
  if [ "\${COMP_CWORD}" -eq 1 ]; then
@@ -1220,6 +1790,11 @@ _atoll_completions() {
1220
1790
  COMPREPLY=( $(compgen -W "\${webhook_cmds}" -- "\${cur}") )
1221
1791
  fi
1222
1792
  ;;
1793
+ feedback)
1794
+ if [ "\${COMP_CWORD}" -eq 2 ]; then
1795
+ COMPREPLY=( $(compgen -W "\${feedback_cmds}" -- "\${cur}") )
1796
+ fi
1797
+ ;;
1223
1798
  completion)
1224
1799
  if [ "\${COMP_CWORD}" -eq 2 ]; then
1225
1800
  COMPREPLY=( $(compgen -W "\${completion_cmds}" -- "\${cur}") )
@@ -1253,6 +1828,10 @@ _atoll() {
1253
1828
  'milestone[Manage milestones]' \\
1254
1829
  'config[Manage CLI configuration]' \\
1255
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]' \\
1256
1835
  'completion[Output shell completion scripts]'
1257
1836
  ;;
1258
1837
  args)
@@ -1261,7 +1840,10 @@ _atoll() {
1261
1840
  _values 'subcommand' \\
1262
1841
  'list[List issues]' \\
1263
1842
  'view[View issue details]' \\
1843
+ 'get[Get issue details]' \\
1264
1844
  'create[Create a new issue]' \\
1845
+ 'archive[Archive an issue]' \\
1846
+ 'unarchive[Unarchive an issue]' \\
1265
1847
  'update[Update an issue]' \\
1266
1848
  'delete[Delete an issue]' \\
1267
1849
  'assign[Assign an issue]' \\
@@ -1271,7 +1853,8 @@ _atoll() {
1271
1853
  _values 'subcommand' \\
1272
1854
  'list[List projects]' \\
1273
1855
  'create[Create a project]' \\
1274
- 'view[View project details]'
1856
+ 'view[View project details]' \\
1857
+ 'get[Get project details]'
1275
1858
  ;;
1276
1859
  milestone)
1277
1860
  _values 'subcommand' \\
@@ -1299,6 +1882,11 @@ _atoll() {
1299
1882
  'create[Create a webhook]' \\
1300
1883
  'delete[Delete a webhook]'
1301
1884
  ;;
1885
+ feedback)
1886
+ _values 'subcommand' \\
1887
+ 'add[Record feedback]' \\
1888
+ 'list[List local feedback]'
1889
+ ;;
1302
1890
  completion)
1303
1891
  _values 'shell' 'bash' 'zsh'
1304
1892
  ;;
@@ -1310,4 +1898,12 @@ _atoll() {
1310
1898
  _atoll "$@"
1311
1899
  `;
1312
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
+ }
1313
1909
  //# sourceMappingURL=index.js.map