atlass 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +17 -0
  2. package/dist/cli.mjs +74 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -176,6 +176,23 @@ A discovery aid for the `--project` filter above: it fetches every project
176
176
  list. An optional query filters by key or name server-side. `--json` emits
177
177
  `{ key, name, id, type, url }` per project.
178
178
 
179
+ ### List Jira statuses
180
+
181
+ ```bash
182
+ atlass jira statuses # every status on the site
183
+ atlass jira statuses progress # filter by name
184
+ atlass jira statuses --project PROJ # statuses used by one project
185
+ atlass jira statuses --json # machine output
186
+ ```
187
+
188
+ A discovery aid for the `--status` filter above. Without `--project` it lists
189
+ every status on the site; with `--project` it lists the statuses that project's
190
+ issue types use (flattened to one list). An optional query filters by name
191
+ (case-insensitive substring). Statuses are collapsed by name and category, then
192
+ ordered by workflow lifecycle (To Do, then In Progress, then Done) and name.
193
+ Prints an aligned `Name Category` list; `--json` emits
194
+ `{ name, id, category, categoryKey }` per status.
195
+
179
196
  ### Search Confluence pages
180
197
 
181
198
  ```bash
package/dist/cli.mjs CHANGED
@@ -8,7 +8,7 @@ import { Entry } from "@napi-rs/keyring";
8
8
  import { marked } from "marked";
9
9
  import { randomUUID } from "node:crypto";
10
10
  //#region package.json
11
- var version = "1.4.0";
11
+ var version = "1.5.0";
12
12
  //#endregion
13
13
  //#region src/api/client.ts
14
14
  var AtlassianClient = class {
@@ -76,20 +76,21 @@ var AtlassianClient = class {
76
76
  return new Uint8Array(await res.arrayBuffer());
77
77
  }
78
78
  };
79
- function httpError(status, path, body = "") {
80
- if (status === 401 || status === 403) return /* @__PURE__ */ new Error("Authentication failed (401/403). Run `atlass auth login` to update your token.");
81
- if (status === 404) return /* @__PURE__ */ new Error(`Not found (404): ${path}`);
82
- if (status === 409) {
83
- const detail = extractError(body);
84
- return /* @__PURE__ */ new Error(`Conflict (409): ${detail || "the page changed on the server"}`);
85
- }
86
- if (status === 413) return /* @__PURE__ */ new Error("Payload too large (413): the page or an attachment exceeds the size limit.");
87
- if (status === 400) {
88
- const detail = extractError(body);
89
- return /* @__PURE__ */ new Error(`Bad request (400): ${detail || path}`);
79
+ var HttpError = class extends Error {
80
+ status;
81
+ constructor(status, message) {
82
+ super(message);
83
+ this.status = status;
84
+ this.name = "HttpError";
90
85
  }
91
- const detail = extractError(body);
92
- return /* @__PURE__ */ new Error(`Request failed (${status}): ${detail || path}`);
86
+ };
87
+ function httpError(status, path, body = "") {
88
+ if (status === 401 || status === 403) return new HttpError(status, "Authentication failed (401/403). Run `atlass auth login` to update your token.");
89
+ if (status === 404) return new HttpError(status, `Not found (404): ${path}`);
90
+ if (status === 409) return new HttpError(status, `Conflict (409): ${extractError(body) || "the page changed on the server"}`);
91
+ if (status === 413) return new HttpError(status, "Payload too large (413): the page or an attachment exceeds the size limit.");
92
+ if (status === 400) return new HttpError(status, `Bad request (400): ${extractError(body) || path}`);
93
+ return new HttpError(status, `Request failed (${status}): ${extractError(body) || path}`);
93
94
  }
94
95
  function extractError(body) {
95
96
  if (!body) return "";
@@ -1338,6 +1339,44 @@ function projectSearchQuery(query, startAt) {
1338
1339
  if (query) params.set("query", query);
1339
1340
  return params.toString();
1340
1341
  }
1342
+ async function listStatuses(client, project) {
1343
+ return dedupeAndSortStatuses((project ? await fetchProjectStatuses(client, project) : await client.getJson("/rest/api/3/status")).map(toStatusSummary));
1344
+ }
1345
+ async function fetchProjectStatuses(client, project) {
1346
+ let groups;
1347
+ try {
1348
+ groups = await client.getJson(`/rest/api/3/project/${encodeURIComponent(project)}/statuses`);
1349
+ } catch (err) {
1350
+ if (err instanceof HttpError && err.status === 404) throw new Error(`No project found with key "${project}".`);
1351
+ throw err;
1352
+ }
1353
+ return groups.flatMap((g) => g.statuses ?? []);
1354
+ }
1355
+ function toStatusSummary(s) {
1356
+ return {
1357
+ name: s.name,
1358
+ id: s.id,
1359
+ category: s.statusCategory?.name ?? "",
1360
+ categoryKey: s.statusCategory?.key ?? ""
1361
+ };
1362
+ }
1363
+ const CATEGORY_ORDER = {
1364
+ new: 0,
1365
+ indeterminate: 1,
1366
+ done: 2
1367
+ };
1368
+ const CATEGORY_LAST = Object.keys(CATEGORY_ORDER).length;
1369
+ function dedupeAndSortStatuses(statuses) {
1370
+ const byNameCategory = /* @__PURE__ */ new Map();
1371
+ for (const s of statuses) {
1372
+ const key = `${s.name}\0${s.categoryKey}`;
1373
+ if (!byNameCategory.has(key)) byNameCategory.set(key, s);
1374
+ }
1375
+ return [...byNameCategory.values()].sort((a, b) => {
1376
+ const rank = (s) => CATEGORY_ORDER[s.categoryKey] ?? CATEGORY_LAST;
1377
+ return rank(a) - rank(b) || a.name.localeCompare(b.name);
1378
+ });
1379
+ }
1341
1380
  function jqlValue(value) {
1342
1381
  return `"${value.replace(/(["\\])/g, "\\$1")}"`;
1343
1382
  }
@@ -1367,6 +1406,26 @@ function formatProjectRows(projects) {
1367
1406
  const width = Math.max(...projects.map((p) => p.key.length));
1368
1407
  return projects.map((p) => `${p.key.padEnd(width)} ${p.name}`);
1369
1408
  }
1409
+ async function jiraStatuses(query, options) {
1410
+ let statuses = await listStatuses(new AtlassianClient(await requireAuth()), options.project);
1411
+ if (query) {
1412
+ const needle = query.toLowerCase();
1413
+ statuses = statuses.filter((s) => s.name.toLowerCase().includes(needle));
1414
+ }
1415
+ if (options.json) {
1416
+ console.log(JSON.stringify(statuses, null, 2));
1417
+ return;
1418
+ }
1419
+ if (statuses.length === 0) {
1420
+ console.log("No matching statuses.");
1421
+ return;
1422
+ }
1423
+ for (const line of formatStatusRows(statuses)) console.log(line);
1424
+ }
1425
+ function formatStatusRows(statuses) {
1426
+ const width = Math.max(...statuses.map((s) => s.name.length));
1427
+ return statuses.map((s) => `${s.name.padEnd(width)} ${s.category}`);
1428
+ }
1370
1429
  async function jiraCopy(arg, options) {
1371
1430
  const auth = await requireAuth();
1372
1431
  const key = await resolveKey(arg);
@@ -1518,6 +1577,7 @@ auth.command("logout").description("Remove stored credentials").action(run(logou
1518
1577
  auth.command("status").description("Show the current login").action(run(status));
1519
1578
  const jira = program.command("jira").description("Jira commands");
1520
1579
  jira.command("projects [query]").description("List projects (optionally filtered by key or name)").option("--json", "output results as JSON").action(run(jiraProjects));
1580
+ jira.command("statuses [query]").description("List statuses (optionally filtered by name, scoped with --project)").option("-p, --project <key>", "limit to statuses used by a project").option("--json", "output results as JSON").action(run(jiraStatuses));
1521
1581
  jira.command("copy [issue]").description("Copy a Jira issue (key or URL) to a Markdown file").option("-o, --out <path>", "output file or directory").action(run(jiraCopy));
1522
1582
  jira.command("update [file]").description("Update a Jira issue description from an edited Markdown file").option("--summary", "also push the H1 as the issue summary").option("-f, --force", "skip the stale-issue and data-loss checks").option("--dry-run", "show what would change without writing").action(run(jiraUpdate));
1523
1583
  jira.command("search [query]").description("Search Jira issues (text query, filters, or --jql)").option("-p, --project <key>", "limit to a project").option("-a, --assignee <who>", "limit to an assignee (or 'me')").option("-s, --status <status>", "limit to a status").option("--jql <jql>", "raw JQL query (ignores other filters)").option("-l, --limit <n>", "max results (default 25, max 100)").option("--json", "output results as JSON").option("-c, --copy", "pick results to copy to Markdown").option("-o, --out <dir>", "output directory for --copy").action(run(jiraSearch));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atlass",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "CLI to copy Jira issues and Confluence pages to Markdown, and update Confluence pages.",
5
5
  "license": "MIT",
6
6
  "repository": {