@sinoia/hubdoc-tools 1.8.3 → 1.9.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/cli.js +900 -41
  2. package/docs/llm-usage.md +95 -0
  3. package/package.json +1 -1
package/cli.js CHANGED
@@ -95034,7 +95034,7 @@ var require_bignumber = __commonJS({
95034
95034
  "node_modules/bignumber.js/bignumber.js"(exports2, module2) {
95035
95035
  (function(globalObject) {
95036
95036
  "use strict";
95037
- var BigNumber, isNumeric = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i, mathceil = Math.ceil, mathfloor = Math.floor, bignumberError = "[BigNumber Error] ", tooManyDigits = bignumberError + "Number primitive has more than 15 significant digits: ", BASE3 = 1e14, LOG_BASE = 14, MAX_SAFE_INTEGER = 9007199254740991, POWS_TEN = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13], SQRT_BASE = 1e7, MAX = 1e9;
95037
+ var BigNumber, isNumeric = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i, mathceil = Math.ceil, mathfloor = Math.floor, bignumberError = "[BigNumber Error] ", tooManyDigits = bignumberError + "Number primitive has more than 15 significant digits: ", BASE4 = 1e14, LOG_BASE = 14, MAX_SAFE_INTEGER = 9007199254740991, POWS_TEN = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13], SQRT_BASE = 1e7, MAX = 1e9;
95038
95038
  function clone(configObject) {
95039
95039
  var div, convertBase, parseNumeric, P = BigNumber2.prototype = { constructor: BigNumber2, toString: null, valueOf: null }, ONE = new BigNumber2(1), DECIMAL_PLACES = 20, ROUNDING_MODE = 4, TO_EXP_NEG = -7, TO_EXP_POS = 21, MIN_EXP = -1e7, MAX_EXP = 1e7, CRYPTO = false, MODULO_MODE = 1, POW_PRECISION = 0, FORMAT = {
95040
95040
  prefix: "",
@@ -95286,7 +95286,7 @@ var require_bignumber = __commonJS({
95286
95286
  if (String(c3[0]).length == i3) {
95287
95287
  for (i3 = 0; i3 < c3.length; i3++) {
95288
95288
  n3 = c3[i3];
95289
- if (n3 < 0 || n3 >= BASE3 || n3 !== mathfloor(n3)) break out;
95289
+ if (n3 < 0 || n3 >= BASE4 || n3 !== mathfloor(n3)) break out;
95290
95290
  }
95291
95291
  if (n3 !== 0) return true;
95292
95292
  }
@@ -95503,7 +95503,7 @@ var require_bignumber = __commonJS({
95503
95503
  e3 = x3.e - y2.e;
95504
95504
  s3 = dp + e3 + 1;
95505
95505
  if (!base) {
95506
- base = BASE3;
95506
+ base = BASE4;
95507
95507
  e3 = bitFloor(x3.e / LOG_BASE) - bitFloor(y2.e / LOG_BASE);
95508
95508
  s3 = s3 / LOG_BASE | 0;
95509
95509
  }
@@ -95582,7 +95582,7 @@ var require_bignumber = __commonJS({
95582
95582
  more = rem[0] != null;
95583
95583
  if (!qc[0]) qc.splice(0, 1);
95584
95584
  }
95585
- if (base == BASE3) {
95585
+ if (base == BASE4) {
95586
95586
  for (i3 = 1, s3 = qc[0]; s3 >= 10; s3 /= 10, i3++) ;
95587
95587
  round(q3, dp + (q3.e = i3 + e3 * LOG_BASE - 1) + 1, rm, more);
95588
95588
  } else {
@@ -95636,7 +95636,7 @@ var require_bignumber = __commonJS({
95636
95636
  }
95637
95637
  return x3;
95638
95638
  }
95639
- function normalise(n3, c3, e3) {
95639
+ function normalise2(n3, c3, e3) {
95640
95640
  var i3 = 1, j3 = c3.length;
95641
95641
  for (; !c3[--j3]; c3.pop()) ;
95642
95642
  for (j3 = c3[0]; j3 >= 10; j3 /= 10, i3++) ;
@@ -95741,12 +95741,12 @@ var require_bignumber = __commonJS({
95741
95741
  for (k3 = 1; j3 >= 10; j3 /= 10, k3++) ;
95742
95742
  if (i3 != k3) {
95743
95743
  x3.e++;
95744
- if (xc[0] == BASE3) xc[0] = 1;
95744
+ if (xc[0] == BASE4) xc[0] = 1;
95745
95745
  }
95746
95746
  break;
95747
95747
  } else {
95748
95748
  xc[ni] += k3;
95749
- if (xc[ni] != BASE3) break;
95749
+ if (xc[ni] != BASE4) break;
95750
95750
  xc[ni--] = 0;
95751
95751
  k3 = 1;
95752
95752
  }
@@ -95955,12 +95955,12 @@ var require_bignumber = __commonJS({
95955
95955
  }
95956
95956
  b3 = (j3 = yc.length) - (i3 = xc.length);
95957
95957
  if (b3 > 0) for (; b3--; xc[i3++] = 0) ;
95958
- b3 = BASE3 - 1;
95958
+ b3 = BASE4 - 1;
95959
95959
  for (; j3 > a3; ) {
95960
95960
  if (xc[--j3] < yc[j3]) {
95961
95961
  for (i3 = j3; i3 && !xc[--i3]; xc[i3] = b3) ;
95962
95962
  --xc[i3];
95963
- xc[j3] += BASE3;
95963
+ xc[j3] += BASE4;
95964
95964
  }
95965
95965
  xc[j3] -= yc[j3];
95966
95966
  }
@@ -95970,7 +95970,7 @@ var require_bignumber = __commonJS({
95970
95970
  y2.c = [y2.e = 0];
95971
95971
  return y2;
95972
95972
  }
95973
- return normalise(y2, xc, ye);
95973
+ return normalise2(y2, xc, ye);
95974
95974
  };
95975
95975
  P.modulo = P.mod = function(y2, b3) {
95976
95976
  var q3, s3, x3 = this;
@@ -96022,7 +96022,7 @@ var require_bignumber = __commonJS({
96022
96022
  ycL = i3;
96023
96023
  }
96024
96024
  for (i3 = xcL + ycL, zc = []; i3--; zc.push(0)) ;
96025
- base = BASE3;
96025
+ base = BASE4;
96026
96026
  sqrtBase = SQRT_BASE;
96027
96027
  for (i3 = ycL; --i3 >= 0; ) {
96028
96028
  c3 = 0;
@@ -96043,7 +96043,7 @@ var require_bignumber = __commonJS({
96043
96043
  } else {
96044
96044
  zc.splice(0, 1);
96045
96045
  }
96046
- return normalise(y2, zc, e3);
96046
+ return normalise2(y2, zc, e3);
96047
96047
  };
96048
96048
  P.negated = function() {
96049
96049
  var x3 = new BigNumber2(this);
@@ -96088,14 +96088,14 @@ var require_bignumber = __commonJS({
96088
96088
  b3 = a3;
96089
96089
  }
96090
96090
  for (a3 = 0; b3; ) {
96091
- a3 = (xc[--b3] = xc[b3] + yc[b3] + a3) / BASE3 | 0;
96092
- xc[b3] = BASE3 === xc[b3] ? 0 : xc[b3] % BASE3;
96091
+ a3 = (xc[--b3] = xc[b3] + yc[b3] + a3) / BASE4 | 0;
96092
+ xc[b3] = BASE4 === xc[b3] ? 0 : xc[b3] % BASE4;
96093
96093
  }
96094
96094
  if (a3) {
96095
96095
  xc = [a3].concat(xc);
96096
96096
  ++ye;
96097
96097
  }
96098
- return normalise(y2, xc, ye);
96098
+ return normalise2(y2, xc, ye);
96099
96099
  };
96100
96100
  P.precision = P.sd = function(sd, rm) {
96101
96101
  var c3, n3, v6, x3 = this;
@@ -622232,10 +622232,10 @@ var require_tasks = __commonJS({
622232
622232
  exports2.VERSIONS = {
622233
622233
  v1: v1_1.tasks_v1.Tasks
622234
622234
  };
622235
- function tasks(versionOrOptions) {
622235
+ function tasks2(versionOrOptions) {
622236
622236
  return (0, googleapis_common_1.getAPI)("tasks", versionOrOptions, exports2.VERSIONS, this);
622237
622237
  }
622238
- exports2.tasks = tasks;
622238
+ exports2.tasks = tasks2;
622239
622239
  var auth = new googleapis_common_1.AuthPlus();
622240
622240
  exports2.auth = auth;
622241
622241
  var googleapis_common_2 = require_src7();
@@ -682521,19 +682521,19 @@ var HubDocExportService = class {
682521
682521
  const mappings = [];
682522
682522
  for (const doc of documents) {
682523
682523
  const detailedDoc = await this.getDocumentDetails(doc.id);
682524
- const projects = [];
682524
+ const projects2 = [];
682525
682525
  if (detailedDoc.project && detailedDoc.folder_path) {
682526
- projects.push({
682526
+ projects2.push({
682527
682527
  projectName: detailedDoc.project.name,
682528
682528
  folderPath: detailedDoc.folder_path,
682529
682529
  fullPath: `${detailedDoc.project.name}/${detailedDoc.folder_path}`
682530
682530
  });
682531
682531
  }
682532
- const defaultTargetPath = projects.length > 0 ? projects[0].fullPath.replace(/[/\\]/g, import_path5.default.sep) : "Exported Documents";
682532
+ const defaultTargetPath = projects2.length > 0 ? projects2[0].fullPath.replace(/[/\\]/g, import_path5.default.sep) : "Exported Documents";
682533
682533
  const mapping = {
682534
682534
  documentId: doc.id,
682535
682535
  documentName: doc.name,
682536
- projects,
682536
+ projects: projects2,
682537
682537
  downloadUrl: doc.download_url,
682538
682538
  metadata: doc.metadata,
682539
682539
  targetPath: defaultTargetPath
@@ -688590,22 +688590,22 @@ var GlobUtil = class {
688590
688590
  return;
688591
688591
  }
688592
688592
  processor.processPatterns(target, patterns);
688593
- let tasks = 1;
688593
+ let tasks2 = 1;
688594
688594
  const next = () => {
688595
- if (--tasks === 0)
688595
+ if (--tasks2 === 0)
688596
688596
  cb();
688597
688597
  };
688598
688598
  for (const [m3, absolute, ifDir] of processor.matches.entries()) {
688599
688599
  if (this.#ignored(m3))
688600
688600
  continue;
688601
- tasks++;
688601
+ tasks2++;
688602
688602
  this.match(m3, absolute, ifDir).then(() => next());
688603
688603
  }
688604
688604
  for (const t3 of processor.subwalkTargets()) {
688605
688605
  if (this.maxDepth !== Infinity && t3.depth() >= this.maxDepth) {
688606
688606
  continue;
688607
688607
  }
688608
- tasks++;
688608
+ tasks2++;
688609
688609
  const childrenCached = t3.readdirCached();
688610
688610
  if (t3.calledReaddir())
688611
688611
  this.walkCB3(t3, childrenCached, processor, next);
@@ -688617,19 +688617,19 @@ var GlobUtil = class {
688617
688617
  }
688618
688618
  walkCB3(target, entries, processor, cb) {
688619
688619
  processor = processor.filterEntries(target, entries);
688620
- let tasks = 1;
688620
+ let tasks2 = 1;
688621
688621
  const next = () => {
688622
- if (--tasks === 0)
688622
+ if (--tasks2 === 0)
688623
688623
  cb();
688624
688624
  };
688625
688625
  for (const [m3, absolute, ifDir] of processor.matches.entries()) {
688626
688626
  if (this.#ignored(m3))
688627
688627
  continue;
688628
- tasks++;
688628
+ tasks2++;
688629
688629
  this.match(m3, absolute, ifDir).then(() => next());
688630
688630
  }
688631
688631
  for (const [target2, patterns] of processor.subwalks.entries()) {
688632
- tasks++;
688632
+ tasks2++;
688633
688633
  this.walkCB2(target2, patterns, processor.child(), next);
688634
688634
  }
688635
688635
  next();
@@ -688649,9 +688649,9 @@ var GlobUtil = class {
688649
688649
  return;
688650
688650
  }
688651
688651
  processor.processPatterns(target, patterns);
688652
- let tasks = 1;
688652
+ let tasks2 = 1;
688653
688653
  const next = () => {
688654
- if (--tasks === 0)
688654
+ if (--tasks2 === 0)
688655
688655
  cb();
688656
688656
  };
688657
688657
  for (const [m3, absolute, ifDir] of processor.matches.entries()) {
@@ -688663,7 +688663,7 @@ var GlobUtil = class {
688663
688663
  if (this.maxDepth !== Infinity && t3.depth() >= this.maxDepth) {
688664
688664
  continue;
688665
688665
  }
688666
- tasks++;
688666
+ tasks2++;
688667
688667
  const children2 = t3.readdirSync();
688668
688668
  this.walkCB3Sync(t3, children2, processor, next);
688669
688669
  }
@@ -688671,9 +688671,9 @@ var GlobUtil = class {
688671
688671
  }
688672
688672
  walkCB3Sync(target, entries, processor, cb) {
688673
688673
  processor = processor.filterEntries(target, entries);
688674
- let tasks = 1;
688674
+ let tasks2 = 1;
688675
688675
  const next = () => {
688676
- if (--tasks === 0)
688676
+ if (--tasks2 === 0)
688677
688677
  cb();
688678
688678
  };
688679
688679
  for (const [m3, absolute, ifDir] of processor.matches.entries()) {
@@ -688682,7 +688682,7 @@ var GlobUtil = class {
688682
688682
  this.matchSync(m3, absolute, ifDir);
688683
688683
  }
688684
688684
  for (const [target2, patterns] of processor.subwalks.entries()) {
688685
- tasks++;
688685
+ tasks2++;
688686
688686
  this.walkCB2Sync(target2, patterns, processor.child(), next);
688687
688687
  }
688688
688688
  next();
@@ -689884,25 +689884,25 @@ var ExportCsvManager = class {
689884
689884
  }).on("end", () => resolve(results)).on("error", reject);
689885
689885
  });
689886
689886
  }
689887
- serializeProjects(projects) {
689888
- return projects.map((p3) => `${p3.projectName}:${p3.folderPath}`).join("|");
689887
+ serializeProjects(projects2) {
689888
+ return projects2.map((p3) => `${p3.projectName}:${p3.folderPath}`).join("|");
689889
689889
  }
689890
689890
  parseProjects(projectsStr) {
689891
689891
  if (!projectsStr || projectsStr.trim() === "") return [];
689892
689892
  try {
689893
- const projects = [];
689893
+ const projects2 = [];
689894
689894
  const projectEntries = projectsStr.split("|");
689895
689895
  for (const entry of projectEntries) {
689896
689896
  const [projectName, folderPath] = entry.split(":");
689897
689897
  if (projectName && folderPath) {
689898
- projects.push({
689898
+ projects2.push({
689899
689899
  projectName: projectName.trim(),
689900
689900
  folderPath: folderPath.trim(),
689901
689901
  fullPath: `${projectName.trim()}/${folderPath.trim()}`
689902
689902
  });
689903
689903
  }
689904
689904
  }
689905
- return projects;
689905
+ return projects2;
689906
689906
  } catch {
689907
689907
  console.warn(import_chalk14.default.yellow(`Warning: Invalid projects format: ${projectsStr}`));
689908
689908
  return [];
@@ -733953,10 +733953,769 @@ async function handlePermissionsShow(id, opts) {
733953
733953
  }
733954
733954
  }
733955
733955
 
733956
+ // apps/cli/cli/handlers/_pagination.ts
733957
+ function extractPagination(headers, itemsLength = 0) {
733958
+ const h3 = normalise(headers);
733959
+ const parseInt10 = (v6, fallback) => {
733960
+ if (typeof v6 === "number") return Number.isFinite(v6) ? v6 : fallback;
733961
+ if (typeof v6 === "string" && v6.trim() !== "") {
733962
+ const n3 = Number(v6);
733963
+ if (Number.isFinite(n3)) return n3;
733964
+ }
733965
+ return fallback;
733966
+ };
733967
+ const page = parseInt10(h3["x-page"], 1);
733968
+ const per_page = parseInt10(h3["x-per-page"], itemsLength);
733969
+ const count = parseInt10(h3["x-total-count"], itemsLength);
733970
+ const pages = parseInt10(h3["x-total-pages"], 1);
733971
+ const rawHasMore = h3["x-has-more"];
733972
+ const has_more = typeof rawHasMore === "string" ? rawHasMore.toLowerCase() === "true" : Boolean(rawHasMore);
733973
+ return { page, per_page, pages, count, has_more };
733974
+ }
733975
+ function normalise(headers) {
733976
+ if (!headers) return {};
733977
+ const out = {};
733978
+ for (const key of Object.keys(headers)) {
733979
+ out[key.toLowerCase()] = headers[key];
733980
+ }
733981
+ return out;
733982
+ }
733983
+
733984
+ // apps/cli/cli/handlers/project-handlers.ts
733985
+ var BASE3 = "/api/v1/projects";
733986
+ var LIST_COLUMNS2 = [
733987
+ { header: "ID", key: "id" },
733988
+ { header: "Name", key: "name", maxWidth: 40 },
733989
+ { header: "Type", key: "project_type" },
733990
+ { header: "Status", key: "status" },
733991
+ { header: "Workspace", key: "workspace_id" },
733992
+ { header: "Progress", key: "completion_percentage", format: (v6) => v6 == null ? "" : `${v6}%` },
733993
+ { header: "Updated", key: "updated_at" }
733994
+ ];
733995
+ function parseJsonOpt5(name, raw, out) {
733996
+ if (raw == null) return void 0;
733997
+ try {
733998
+ return JSON.parse(raw);
733999
+ } catch (err) {
734000
+ out.fail(new Error(`Invalid JSON for --${name}: ${err.message}`));
734001
+ }
734002
+ }
734003
+ async function handleProjectsList(opts) {
734004
+ const out = output(opts);
734005
+ const ctx = await buildApiContext(out);
734006
+ const params = {};
734007
+ if (opts.workspace !== void 0) {
734008
+ params.workspace_id = opts.workspace === "null" ? "null" : opts.workspace;
734009
+ }
734010
+ if (opts.type) params.project_type = opts.type;
734011
+ if (opts.status) params.status = opts.status;
734012
+ if (opts.page) params.page = Number(opts.page);
734013
+ if (opts.perPage) params.per_page = Number(opts.perPage);
734014
+ try {
734015
+ const res = await ctx.axios.get(BASE3, { params });
734016
+ const items = Array.isArray(res.data) ? res.data : [];
734017
+ const pagination = extractPagination(res.headers, items.length);
734018
+ if (opts.json) {
734019
+ out.show({ items, pagination });
734020
+ return;
734021
+ }
734022
+ out.list(items, LIST_COLUMNS2);
734023
+ if (pagination.pages > 1) {
734024
+ out.note(`page ${pagination.page}/${pagination.pages} (${pagination.count} total)`);
734025
+ }
734026
+ } catch (err) {
734027
+ out.fail(err);
734028
+ }
734029
+ }
734030
+ async function handleProjectsShow(id, opts) {
734031
+ const out = output(opts);
734032
+ const ctx = await buildApiContext(out);
734033
+ try {
734034
+ const res = await ctx.axios.get(`${BASE3}/${encodeURIComponent(id)}`);
734035
+ out.show(res.data?.project ?? res.data, { title: `Project ${id}` });
734036
+ } catch (err) {
734037
+ out.fail(err);
734038
+ }
734039
+ }
734040
+ async function handleProjectsCreate(opts) {
734041
+ const out = output(opts);
734042
+ const ctx = await buildApiContext(out);
734043
+ if (!opts.name) out.fail(new Error("--name is required"));
734044
+ const body2 = { name: opts.name };
734045
+ if (opts.description !== void 0) body2.description = opts.description;
734046
+ if (opts.workspace !== void 0) {
734047
+ body2.workspace_id = opts.workspace === "null" ? null : opts.workspace;
734048
+ }
734049
+ if (opts.typeId) body2.project_type_id = opts.typeId;
734050
+ if (opts.folder) body2.documents_folder_id = opts.folder;
734051
+ const settings = parseJsonOpt5("settings", opts.settings, out);
734052
+ if (settings !== void 0) body2.settings = settings;
734053
+ const metadata2 = parseJsonOpt5("metadata", opts.metadata, out);
734054
+ if (metadata2 !== void 0) body2.metadata_user = metadata2;
734055
+ try {
734056
+ const res = await ctx.axios.post(BASE3, body2);
734057
+ const project = res.data?.project ?? res.data;
734058
+ out.ok(`Project created (id=${project?.id ?? "unknown"})`, project);
734059
+ } catch (err) {
734060
+ out.fail(err);
734061
+ }
734062
+ }
734063
+ async function handleProjectsUpdate(id, opts) {
734064
+ const out = output(opts);
734065
+ const ctx = await buildApiContext(out);
734066
+ const body2 = {};
734067
+ if (opts.name !== void 0) body2.name = opts.name;
734068
+ if (opts.description !== void 0) body2.description = opts.description;
734069
+ if (opts.status !== void 0) body2.status = opts.status;
734070
+ const settings = parseJsonOpt5("settings", opts.settings, out);
734071
+ if (settings !== void 0) body2.settings = settings;
734072
+ const metadata2 = parseJsonOpt5("metadata", opts.metadata, out);
734073
+ if (metadata2 !== void 0) body2.metadata_user = metadata2;
734074
+ if (Object.keys(body2).length === 0) out.fail(new Error("No fields to update"));
734075
+ try {
734076
+ const res = await ctx.axios.patch(`${BASE3}/${encodeURIComponent(id)}`, body2);
734077
+ out.ok(`Project ${id} updated`, res.data?.project ?? res.data);
734078
+ } catch (err) {
734079
+ out.fail(err);
734080
+ }
734081
+ }
734082
+ async function handleProjectsDelete(id, opts) {
734083
+ const out = output(opts);
734084
+ const ctx = await buildApiContext(out);
734085
+ if (!opts.yes && !opts.json) {
734086
+ const { confirm } = await lib_default.prompt([
734087
+ {
734088
+ type: "confirm",
734089
+ name: "confirm",
734090
+ message: `Delete project ${id}? This also drops its sprints and tickets.`,
734091
+ default: false
734092
+ }
734093
+ ]);
734094
+ if (!confirm) {
734095
+ out.note("Aborted");
734096
+ return;
734097
+ }
734098
+ } else if (!opts.yes && opts.json) {
734099
+ out.fail(new Error("Refusing to delete without --yes in --json mode"));
734100
+ }
734101
+ try {
734102
+ await ctx.axios.delete(`${BASE3}/${encodeURIComponent(id)}`);
734103
+ out.ok(`Project ${id} deleted`);
734104
+ } catch (err) {
734105
+ out.fail(err);
734106
+ }
734107
+ }
734108
+ async function handleProjectsArchive(id, opts) {
734109
+ const out = output(opts);
734110
+ const ctx = await buildApiContext(out);
734111
+ try {
734112
+ const res = await ctx.axios.post(`${BASE3}/${encodeURIComponent(id)}/archive`);
734113
+ out.ok(`Project ${id} archived`, res.data?.project ?? res.data);
734114
+ } catch (err) {
734115
+ out.fail(err);
734116
+ }
734117
+ }
734118
+ async function handleProjectsPublish(id, opts) {
734119
+ const out = output(opts);
734120
+ const ctx = await buildApiContext(out);
734121
+ try {
734122
+ const res = await ctx.axios.post(`${BASE3}/${encodeURIComponent(id)}/publish`);
734123
+ out.ok(`Project ${id} published`, res.data?.project ?? res.data);
734124
+ } catch (err) {
734125
+ out.fail(err);
734126
+ }
734127
+ }
734128
+
734129
+ // apps/cli/cli/handlers/sprint-handlers.ts
734130
+ var sprintsBase = (pid) => `/api/v1/projects/${encodeURIComponent(pid)}/sprints`;
734131
+ var SPRINT_COLUMNS = [
734132
+ { header: "ID", key: "id" },
734133
+ { header: "Name", key: "name", maxWidth: 30 },
734134
+ { header: "Status", key: "status" },
734135
+ { header: "Start", key: "start_date" },
734136
+ { header: "End", key: "end_date" },
734137
+ { header: "Position", key: "position" }
734138
+ ];
734139
+ function requireProject(opts, out) {
734140
+ if (!opts.project) out.fail(new Error("--project <id> is required"));
734141
+ }
734142
+ async function handleSprintsList(opts) {
734143
+ const out = output(opts);
734144
+ requireProject(opts, out);
734145
+ const ctx = await buildApiContext(out);
734146
+ const params = {};
734147
+ if (opts.status) params.status = opts.status;
734148
+ if (opts.page) params.page = Number(opts.page);
734149
+ if (opts.perPage) params.per_page = Number(opts.perPage);
734150
+ try {
734151
+ const res = await ctx.axios.get(sprintsBase(opts.project), { params });
734152
+ const items = Array.isArray(res.data) ? res.data : [];
734153
+ const pagination = extractPagination(res.headers, items.length);
734154
+ if (opts.json) {
734155
+ out.show({ items, pagination });
734156
+ return;
734157
+ }
734158
+ out.list(items, SPRINT_COLUMNS);
734159
+ if (pagination.pages > 1) {
734160
+ out.note(`page ${pagination.page}/${pagination.pages} (${pagination.count} total)`);
734161
+ }
734162
+ } catch (err) {
734163
+ out.fail(err);
734164
+ }
734165
+ }
734166
+ async function handleSprintsShow(id, opts) {
734167
+ const out = output(opts);
734168
+ requireProject(opts, out);
734169
+ const ctx = await buildApiContext(out);
734170
+ try {
734171
+ const res = await ctx.axios.get(`${sprintsBase(opts.project)}/${encodeURIComponent(id)}`);
734172
+ out.show(res.data?.sprint ?? res.data, { title: `Sprint ${id}` });
734173
+ } catch (err) {
734174
+ out.fail(err);
734175
+ }
734176
+ }
734177
+ async function handleSprintsCreate(opts) {
734178
+ const out = output(opts);
734179
+ requireProject(opts, out);
734180
+ const ctx = await buildApiContext(out);
734181
+ if (!opts.name) out.fail(new Error("--name is required"));
734182
+ const body2 = { name: opts.name };
734183
+ if (opts.goal !== void 0) body2.goal = opts.goal;
734184
+ if (opts.description !== void 0) body2.description = opts.description;
734185
+ if (opts.start !== void 0) body2.start_date = opts.start;
734186
+ if (opts.end !== void 0) body2.end_date = opts.end;
734187
+ try {
734188
+ const res = await ctx.axios.post(sprintsBase(opts.project), body2);
734189
+ const sprint = res.data?.sprint ?? res.data;
734190
+ out.ok(`Sprint created (id=${sprint?.id ?? "unknown"})`, sprint);
734191
+ } catch (err) {
734192
+ out.fail(err);
734193
+ }
734194
+ }
734195
+ async function handleSprintsUpdate(id, opts) {
734196
+ const out = output(opts);
734197
+ requireProject(opts, out);
734198
+ const ctx = await buildApiContext(out);
734199
+ const body2 = {};
734200
+ if (opts.name !== void 0) body2.name = opts.name;
734201
+ if (opts.goal !== void 0) body2.goal = opts.goal;
734202
+ if (opts.description !== void 0) body2.description = opts.description;
734203
+ if (opts.start !== void 0) body2.start_date = opts.start;
734204
+ if (opts.end !== void 0) body2.end_date = opts.end;
734205
+ if (opts.status !== void 0) body2.status = opts.status;
734206
+ if (Object.keys(body2).length === 0) out.fail(new Error("No fields to update"));
734207
+ try {
734208
+ const res = await ctx.axios.patch(
734209
+ `${sprintsBase(opts.project)}/${encodeURIComponent(id)}`,
734210
+ body2
734211
+ );
734212
+ out.ok(`Sprint ${id} updated`, res.data?.sprint ?? res.data);
734213
+ } catch (err) {
734214
+ out.fail(err);
734215
+ }
734216
+ }
734217
+ async function handleSprintsDelete(id, opts) {
734218
+ const out = output(opts);
734219
+ requireProject(opts, out);
734220
+ const ctx = await buildApiContext(out);
734221
+ if (!opts.yes && !opts.json) {
734222
+ const { confirm } = await lib_default.prompt([
734223
+ {
734224
+ type: "confirm",
734225
+ name: "confirm",
734226
+ message: `Delete sprint ${id}? Tickets in this sprint will be returned to the backlog.`,
734227
+ default: false
734228
+ }
734229
+ ]);
734230
+ if (!confirm) {
734231
+ out.note("Aborted");
734232
+ return;
734233
+ }
734234
+ } else if (!opts.yes && opts.json) {
734235
+ out.fail(new Error("Refusing to delete without --yes in --json mode"));
734236
+ }
734237
+ try {
734238
+ await ctx.axios.delete(`${sprintsBase(opts.project)}/${encodeURIComponent(id)}`);
734239
+ out.ok(`Sprint ${id} deleted`);
734240
+ } catch (err) {
734241
+ out.fail(err);
734242
+ }
734243
+ }
734244
+ async function handleSprintsStart(id, opts) {
734245
+ const out = output(opts);
734246
+ requireProject(opts, out);
734247
+ const ctx = await buildApiContext(out);
734248
+ try {
734249
+ const res = await ctx.axios.post(
734250
+ `${sprintsBase(opts.project)}/${encodeURIComponent(id)}/start`
734251
+ );
734252
+ out.ok(`Sprint ${id} started`, res.data?.sprint ?? res.data);
734253
+ } catch (err) {
734254
+ out.fail(err);
734255
+ }
734256
+ }
734257
+ async function handleSprintsComplete(id, opts) {
734258
+ const out = output(opts);
734259
+ requireProject(opts, out);
734260
+ const ctx = await buildApiContext(out);
734261
+ try {
734262
+ const res = await ctx.axios.post(
734263
+ `${sprintsBase(opts.project)}/${encodeURIComponent(id)}/complete`
734264
+ );
734265
+ out.ok(`Sprint ${id} completed`, res.data?.sprint ?? res.data);
734266
+ } catch (err) {
734267
+ out.fail(err);
734268
+ }
734269
+ }
734270
+
734271
+ // apps/cli/cli/handlers/ticket-handlers.ts
734272
+ var ticketsBase = (pid) => `/api/v1/projects/${encodeURIComponent(pid)}/tickets`;
734273
+ var TICKET_COLUMNS = [
734274
+ { header: "ID", key: "id" },
734275
+ { header: "Title", key: "title", maxWidth: 40 },
734276
+ { header: "Type", key: "ticket_type" },
734277
+ { header: "Status", key: "status" },
734278
+ { header: "Priority", key: "priority" },
734279
+ { header: "Sprint", key: "sprint_id" },
734280
+ { header: "Assignee", key: "assignee_id" },
734281
+ { header: "Due", key: "due_date" }
734282
+ ];
734283
+ function requireProject2(opts, out) {
734284
+ if (!opts.project) out.fail(new Error("--project <id> is required"));
734285
+ }
734286
+ function parseLabels(raw) {
734287
+ if (raw == null) return void 0;
734288
+ return raw.split(",").map((s3) => s3.trim()).filter(Boolean);
734289
+ }
734290
+ async function handleTicketsList(opts) {
734291
+ const out = output(opts);
734292
+ requireProject2(opts, out);
734293
+ const ctx = await buildApiContext(out);
734294
+ const params = {};
734295
+ if (opts.backlog) {
734296
+ params.backlog = true;
734297
+ } else if (opts.sprint !== void 0) {
734298
+ params.sprint_id = opts.sprint === "null" ? "null" : opts.sprint;
734299
+ }
734300
+ if (opts.assignee !== void 0) {
734301
+ params.assignee_id = opts.assignee === "null" ? "null" : opts.assignee;
734302
+ }
734303
+ if (opts.status) params.status = opts.status;
734304
+ if (opts.priority) params.priority = opts.priority;
734305
+ if (opts.label) params.label = opts.label;
734306
+ if (opts.page) params.page = Number(opts.page);
734307
+ if (opts.perPage) params.per_page = Number(opts.perPage);
734308
+ try {
734309
+ const res = await ctx.axios.get(ticketsBase(opts.project), { params });
734310
+ const items = Array.isArray(res.data) ? res.data : [];
734311
+ const pagination = extractPagination(res.headers, items.length);
734312
+ if (opts.json) {
734313
+ out.show({ items, pagination });
734314
+ return;
734315
+ }
734316
+ out.list(items, TICKET_COLUMNS);
734317
+ if (pagination.pages > 1) {
734318
+ out.note(`page ${pagination.page}/${pagination.pages} (${pagination.count} total)`);
734319
+ }
734320
+ } catch (err) {
734321
+ out.fail(err);
734322
+ }
734323
+ }
734324
+ async function handleTicketsShow(id, opts) {
734325
+ const out = output(opts);
734326
+ requireProject2(opts, out);
734327
+ const ctx = await buildApiContext(out);
734328
+ try {
734329
+ const res = await ctx.axios.get(`${ticketsBase(opts.project)}/${encodeURIComponent(id)}`);
734330
+ out.show(res.data?.ticket ?? res.data, { title: `Ticket ${id}` });
734331
+ } catch (err) {
734332
+ out.fail(err);
734333
+ }
734334
+ }
734335
+ function ticketBodyFromOpts(opts, includeAll) {
734336
+ const body2 = {};
734337
+ if (opts.title !== void 0) body2.title = opts.title;
734338
+ if (opts.description !== void 0) body2.description = opts.description;
734339
+ if (opts.type !== void 0) body2.ticket_type = opts.type;
734340
+ if (opts.sprint !== void 0) {
734341
+ body2.sprint_id = opts.sprint === "null" ? null : opts.sprint;
734342
+ }
734343
+ if (opts.assignee !== void 0) {
734344
+ body2.assignee_id = opts.assignee === "null" ? null : opts.assignee;
734345
+ }
734346
+ if (opts.priority !== void 0) body2.priority = opts.priority;
734347
+ if (opts.status !== void 0) body2.status = opts.status;
734348
+ if (opts.parent !== void 0) {
734349
+ body2.parent_ticket_id = opts.parent === "null" ? null : opts.parent;
734350
+ }
734351
+ if (opts.due !== void 0) body2.due_date = opts.due;
734352
+ if (opts.storyPoints !== void 0) body2.story_points = Number(opts.storyPoints);
734353
+ const labels = parseLabels(opts.labels);
734354
+ if (labels !== void 0) body2.labels = labels;
734355
+ if (opts.column !== void 0) body2.column_status = opts.column;
734356
+ return includeAll ? body2 : body2;
734357
+ }
734358
+ async function handleTicketsCreate(opts) {
734359
+ const out = output(opts);
734360
+ requireProject2(opts, out);
734361
+ const ctx = await buildApiContext(out);
734362
+ if (!opts.title) out.fail(new Error("--title is required"));
734363
+ const body2 = ticketBodyFromOpts(opts, true);
734364
+ try {
734365
+ const res = await ctx.axios.post(ticketsBase(opts.project), body2);
734366
+ const ticket = res.data?.ticket ?? res.data;
734367
+ out.ok(`Ticket created (id=${ticket?.id ?? "unknown"})`, ticket);
734368
+ } catch (err) {
734369
+ out.fail(err);
734370
+ }
734371
+ }
734372
+ async function handleTicketsUpdate(id, opts) {
734373
+ const out = output(opts);
734374
+ requireProject2(opts, out);
734375
+ const ctx = await buildApiContext(out);
734376
+ const body2 = ticketBodyFromOpts(opts, true);
734377
+ if (Object.keys(body2).length === 0) out.fail(new Error("No fields to update"));
734378
+ try {
734379
+ const res = await ctx.axios.patch(
734380
+ `${ticketsBase(opts.project)}/${encodeURIComponent(id)}`,
734381
+ body2
734382
+ );
734383
+ out.ok(`Ticket ${id} updated`, res.data?.ticket ?? res.data);
734384
+ } catch (err) {
734385
+ out.fail(err);
734386
+ }
734387
+ }
734388
+ async function handleTicketsDelete(id, opts) {
734389
+ const out = output(opts);
734390
+ requireProject2(opts, out);
734391
+ const ctx = await buildApiContext(out);
734392
+ if (!opts.yes && !opts.json) {
734393
+ const { confirm } = await lib_default.prompt([
734394
+ {
734395
+ type: "confirm",
734396
+ name: "confirm",
734397
+ message: `Delete ticket ${id}? This also drops its tasks, subtasks, deliverables and activities.`,
734398
+ default: false
734399
+ }
734400
+ ]);
734401
+ if (!confirm) {
734402
+ out.note("Aborted");
734403
+ return;
734404
+ }
734405
+ } else if (!opts.yes && opts.json) {
734406
+ out.fail(new Error("Refusing to delete without --yes in --json mode"));
734407
+ }
734408
+ try {
734409
+ await ctx.axios.delete(`${ticketsBase(opts.project)}/${encodeURIComponent(id)}`);
734410
+ out.ok(`Ticket ${id} deleted`);
734411
+ } catch (err) {
734412
+ out.fail(err);
734413
+ }
734414
+ }
734415
+ async function handleTicketsAssign(id, opts) {
734416
+ const out = output(opts);
734417
+ requireProject2(opts, out);
734418
+ const ctx = await buildApiContext(out);
734419
+ if (opts.to === void 0) out.fail(new Error("--to <user-id|null> is required"));
734420
+ const body2 = { assignee_id: opts.to === "null" ? null : opts.to };
734421
+ try {
734422
+ const res = await ctx.axios.patch(
734423
+ `${ticketsBase(opts.project)}/${encodeURIComponent(id)}/assign`,
734424
+ body2
734425
+ );
734426
+ const t3 = res.data?.ticket ?? res.data;
734427
+ out.ok(
734428
+ body2.assignee_id === null ? `Ticket ${id} unassigned` : `Ticket ${id} assigned to ${body2.assignee_id}`,
734429
+ t3
734430
+ );
734431
+ } catch (err) {
734432
+ out.fail(err);
734433
+ }
734434
+ }
734435
+ async function handleTicketsStatus(id, opts) {
734436
+ const out = output(opts);
734437
+ requireProject2(opts, out);
734438
+ const ctx = await buildApiContext(out);
734439
+ if (!opts.to) {
734440
+ out.fail(new Error("--to <open|in_progress|done|blocked|awaiting_human|cancelled|failed> is required"));
734441
+ }
734442
+ const body2 = { status: opts.to };
734443
+ if (opts.column !== void 0) body2.column_status = opts.column;
734444
+ try {
734445
+ const res = await ctx.axios.patch(
734446
+ `${ticketsBase(opts.project)}/${encodeURIComponent(id)}/change_status`,
734447
+ body2
734448
+ );
734449
+ out.ok(`Ticket ${id} status \u2192 ${opts.to}`, res.data?.ticket ?? res.data);
734450
+ } catch (err) {
734451
+ out.fail(err);
734452
+ }
734453
+ }
734454
+ async function handleTicketsMove(id, opts) {
734455
+ const out = output(opts);
734456
+ requireProject2(opts, out);
734457
+ const ctx = await buildApiContext(out);
734458
+ const body2 = {};
734459
+ if (opts.backlog) {
734460
+ body2.sprint_id = null;
734461
+ } else if (opts.sprint !== void 0) {
734462
+ body2.sprint_id = opts.sprint === "null" ? null : opts.sprint;
734463
+ }
734464
+ if (opts.column !== void 0) body2.column_status = opts.column;
734465
+ if (opts.position !== void 0) body2.position = Number(opts.position);
734466
+ if (Object.keys(body2).length === 0) {
734467
+ out.fail(new Error("Nothing to move \u2014 pass --sprint, --backlog, --column or --position"));
734468
+ }
734469
+ try {
734470
+ const res = await ctx.axios.patch(
734471
+ `${ticketsBase(opts.project)}/${encodeURIComponent(id)}/move`,
734472
+ body2
734473
+ );
734474
+ out.ok(`Ticket ${id} moved`, res.data?.ticket ?? res.data);
734475
+ } catch (err) {
734476
+ out.fail(err);
734477
+ }
734478
+ }
734479
+ var ACTIVITY_COLUMNS = [
734480
+ { header: "ID", key: "id" },
734481
+ { header: "Kind", key: "kind" },
734482
+ { header: "Actor", key: "actor_id" },
734483
+ { header: "At", key: "created_at" },
734484
+ { header: "Detail", key: "summary", maxWidth: 60 }
734485
+ ];
734486
+ async function handleTicketsActivities(id, opts) {
734487
+ const out = output(opts);
734488
+ requireProject2(opts, out);
734489
+ const ctx = await buildApiContext(out);
734490
+ const params = {};
734491
+ if (opts.page) params.page = Number(opts.page);
734492
+ if (opts.perPage) params.per_page = Number(opts.perPage);
734493
+ try {
734494
+ const res = await ctx.axios.get(
734495
+ `${ticketsBase(opts.project)}/${encodeURIComponent(id)}/activities`,
734496
+ { params }
734497
+ );
734498
+ const items = Array.isArray(res.data) ? res.data : [];
734499
+ const pagination = extractPagination(res.headers, items.length);
734500
+ if (opts.json) {
734501
+ out.show({ items, pagination });
734502
+ return;
734503
+ }
734504
+ out.list(items, ACTIVITY_COLUMNS);
734505
+ if (pagination.pages > 1) {
734506
+ out.note(`page ${pagination.page}/${pagination.pages} (${pagination.count} total)`);
734507
+ }
734508
+ } catch (err) {
734509
+ out.fail(err);
734510
+ }
734511
+ }
734512
+ async function handleTicketsDeliverable(id, opts) {
734513
+ const out = output(opts);
734514
+ requireProject2(opts, out);
734515
+ const ctx = await buildApiContext(out);
734516
+ if (!opts.deliverable) out.fail(new Error("--deliverable <id> is required"));
734517
+ try {
734518
+ const res = await ctx.axios.post(
734519
+ `${ticketsBase(opts.project)}/${encodeURIComponent(id)}/publish_deliverable`,
734520
+ { deliverable_id: opts.deliverable }
734521
+ );
734522
+ out.ok(
734523
+ `Deliverable ${opts.deliverable} published on ticket ${id}`,
734524
+ res.data?.deliverable ?? res.data
734525
+ );
734526
+ } catch (err) {
734527
+ out.fail(err);
734528
+ }
734529
+ }
734530
+ async function handleTicketsDispatch(id, opts) {
734531
+ const out = output(opts);
734532
+ requireProject2(opts, out);
734533
+ const ctx = await buildApiContext(out);
734534
+ try {
734535
+ const res = await ctx.axios.post(
734536
+ `${ticketsBase(opts.project)}/${encodeURIComponent(id)}/dispatch_agent`
734537
+ );
734538
+ out.ok(`Agent dispatched on ticket ${id}`, res.data?.ticket ?? res.data);
734539
+ } catch (err) {
734540
+ out.fail(err);
734541
+ }
734542
+ }
734543
+
734544
+ // apps/cli/cli/handlers/task-handlers.ts
734545
+ var tasksBase = (pid, tid) => `/api/v1/projects/${encodeURIComponent(pid)}/tickets/${encodeURIComponent(tid)}/tasks`;
734546
+ var TASK_COLUMNS = [
734547
+ { header: "ID", key: "id" },
734548
+ { header: "Title", key: "title", maxWidth: 40 },
734549
+ { header: "Done", key: "completed", format: (v6) => v6 ? "\u2714" : "" },
734550
+ { header: "Priority", key: "priority" },
734551
+ { header: "Assignee", key: "assignee_id" },
734552
+ { header: "Due", key: "due_date" },
734553
+ { header: "Position", key: "position" }
734554
+ ];
734555
+ function requireParents(opts, out) {
734556
+ if (!opts.project) out.fail(new Error("--project <id> is required"));
734557
+ if (!opts.ticket) out.fail(new Error("--ticket <id> is required"));
734558
+ }
734559
+ function parseLabels2(raw) {
734560
+ if (raw == null) return void 0;
734561
+ return raw.split(",").map((s3) => s3.trim()).filter(Boolean);
734562
+ }
734563
+ async function handleTasksList(opts) {
734564
+ const out = output(opts);
734565
+ requireParents(opts, out);
734566
+ const ctx = await buildApiContext(out);
734567
+ try {
734568
+ const res = await ctx.axios.get(tasksBase(opts.project, opts.ticket));
734569
+ const items = Array.isArray(res.data) ? res.data : [];
734570
+ const pagination = extractPagination(res.headers, items.length);
734571
+ if (opts.json) {
734572
+ out.show({ items, pagination });
734573
+ return;
734574
+ }
734575
+ out.list(items, TASK_COLUMNS);
734576
+ if (pagination.pages > 1) {
734577
+ out.note(`page ${pagination.page}/${pagination.pages} (${pagination.count} total)`);
734578
+ }
734579
+ } catch (err) {
734580
+ out.fail(err);
734581
+ }
734582
+ }
734583
+ async function handleTasksShow(id, opts) {
734584
+ const out = output(opts);
734585
+ requireParents(opts, out);
734586
+ const ctx = await buildApiContext(out);
734587
+ try {
734588
+ const res = await ctx.axios.get(
734589
+ `${tasksBase(opts.project, opts.ticket)}/${encodeURIComponent(id)}`
734590
+ );
734591
+ out.show(res.data?.task ?? res.data, { title: `Task ${id}` });
734592
+ } catch (err) {
734593
+ out.fail(err);
734594
+ }
734595
+ }
734596
+ function taskBodyFromOpts(opts) {
734597
+ const body2 = {};
734598
+ if (opts.title !== void 0) body2.title = opts.title;
734599
+ if (opts.description !== void 0) body2.description = opts.description;
734600
+ if (opts.priority !== void 0) body2.priority = opts.priority;
734601
+ if (opts.assignee !== void 0) {
734602
+ body2.assignee_id = opts.assignee === "null" ? null : opts.assignee;
734603
+ }
734604
+ if (opts.due !== void 0) body2.due_date = opts.due;
734605
+ if (opts.estimate !== void 0) body2.estimate = Number(opts.estimate);
734606
+ const labels = parseLabels2(opts.labels);
734607
+ if (labels !== void 0) body2.labels = labels;
734608
+ return body2;
734609
+ }
734610
+ async function handleTasksCreate(opts) {
734611
+ const out = output(opts);
734612
+ requireParents(opts, out);
734613
+ const ctx = await buildApiContext(out);
734614
+ if (!opts.title) out.fail(new Error("--title is required"));
734615
+ const body2 = taskBodyFromOpts(opts);
734616
+ try {
734617
+ const res = await ctx.axios.post(tasksBase(opts.project, opts.ticket), body2);
734618
+ const task = res.data?.task ?? res.data;
734619
+ out.ok(`Task created (id=${task?.id ?? "unknown"})`, task);
734620
+ } catch (err) {
734621
+ out.fail(err);
734622
+ }
734623
+ }
734624
+ async function handleTasksUpdate(id, opts) {
734625
+ const out = output(opts);
734626
+ requireParents(opts, out);
734627
+ const ctx = await buildApiContext(out);
734628
+ const body2 = taskBodyFromOpts(opts);
734629
+ if (Object.keys(body2).length === 0) out.fail(new Error("No fields to update"));
734630
+ try {
734631
+ const res = await ctx.axios.patch(
734632
+ `${tasksBase(opts.project, opts.ticket)}/${encodeURIComponent(id)}`,
734633
+ body2
734634
+ );
734635
+ out.ok(`Task ${id} updated`, res.data?.task ?? res.data);
734636
+ } catch (err) {
734637
+ out.fail(err);
734638
+ }
734639
+ }
734640
+ async function handleTasksDelete(id, opts) {
734641
+ const out = output(opts);
734642
+ requireParents(opts, out);
734643
+ const ctx = await buildApiContext(out);
734644
+ if (!opts.yes && !opts.json) {
734645
+ const { confirm } = await lib_default.prompt([
734646
+ {
734647
+ type: "confirm",
734648
+ name: "confirm",
734649
+ message: `Delete task ${id}?`,
734650
+ default: false
734651
+ }
734652
+ ]);
734653
+ if (!confirm) {
734654
+ out.note("Aborted");
734655
+ return;
734656
+ }
734657
+ } else if (!opts.yes && opts.json) {
734658
+ out.fail(new Error("Refusing to delete without --yes in --json mode"));
734659
+ }
734660
+ try {
734661
+ await ctx.axios.delete(
734662
+ `${tasksBase(opts.project, opts.ticket)}/${encodeURIComponent(id)}`
734663
+ );
734664
+ out.ok(`Task ${id} deleted`);
734665
+ } catch (err) {
734666
+ out.fail(err);
734667
+ }
734668
+ }
734669
+ async function handleTasksComplete(id, opts) {
734670
+ const out = output(opts);
734671
+ requireParents(opts, out);
734672
+ const ctx = await buildApiContext(out);
734673
+ const body2 = { completed: !opts.uncomplete };
734674
+ try {
734675
+ const res = await ctx.axios.patch(
734676
+ `${tasksBase(opts.project, opts.ticket)}/${encodeURIComponent(id)}/complete`,
734677
+ body2
734678
+ );
734679
+ out.ok(
734680
+ body2.completed ? `Task ${id} completed` : `Task ${id} re-opened`,
734681
+ res.data?.task ?? res.data
734682
+ );
734683
+ } catch (err) {
734684
+ out.fail(err);
734685
+ }
734686
+ }
734687
+ async function handleTasksReorder(opts) {
734688
+ const out = output(opts);
734689
+ requireParents(opts, out);
734690
+ const ctx = await buildApiContext(out);
734691
+ if (!opts.order || opts.order.trim() === "") {
734692
+ out.fail(new Error("--order is required (comma-separated task ids)"));
734693
+ }
734694
+ const task_ids = opts.order.split(",").map((s3) => s3.trim()).filter(Boolean);
734695
+ if (task_ids.length === 0) {
734696
+ out.fail(new Error("--order must contain at least one task id"));
734697
+ }
734698
+ try {
734699
+ const res = await ctx.axios.patch(
734700
+ `${tasksBase(opts.project, opts.ticket)}/reorder`,
734701
+ { task_ids }
734702
+ );
734703
+ const items = Array.isArray(res.data) ? res.data : [];
734704
+ if (opts.json) {
734705
+ out.show({ ok: true, message: "Tasks reordered", tasks: items });
734706
+ return;
734707
+ }
734708
+ out.ok("Tasks reordered");
734709
+ out.list(items, TASK_COLUMNS);
734710
+ } catch (err) {
734711
+ out.fail(err);
734712
+ }
734713
+ }
734714
+
733956
734715
  // apps/cli/cli.ts
733957
734716
  var getVersion = () => {
733958
734717
  try {
733959
- if (true) return "1.8.3";
734718
+ if (true) return "1.9.0";
733960
734719
  } catch {
733961
734720
  }
733962
734721
  for (const candidate of [
@@ -734250,6 +735009,106 @@ permissions.command("grant").description("Grant a permission on a resource to a
734250
735009
  permissions.command("revoke <id>").description("Revoke a permission by id").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
734251
735010
  await handlePermissionsRevoke(id, withGlobals(options));
734252
735011
  });
735012
+ var projects = program2.command("projects").description("Manage projects (attached to a workspace or standalone)");
735013
+ projects.command("ls").alias("list").description("List projects").option("--workspace <id>", 'Filter by workspace id; use "null" for standalone projects').option("--type <slug>", "Filter by project_type").option("--status <status>", "active | completed | archived | draft | failed | paused").option("--page <n>").option("--per-page <n>").action(async (options) => {
735014
+ await handleProjectsList(withGlobals(options));
735015
+ });
735016
+ projects.command("show <id>").description("Show a project (with sprints and ticket stats)").action(async (id, options) => {
735017
+ await handleProjectsShow(id, withGlobals(options));
735018
+ });
735019
+ projects.command("create").description("Create a project").requiredOption("--name <name>", "Project name").option("--description <text>").option("--workspace <id>", "Attach to a workspace (omit for standalone)").option("--type-id <id>", "Project type id (UUID)").option("--folder <id>", "Link to a documents folder").option("--settings <json>", "Kanban columns / icon / color as JSON").option("--metadata <json>", "Custom metadata as JSON object").action(async (options) => {
735020
+ await handleProjectsCreate(withGlobals(options));
735021
+ });
735022
+ projects.command("update <id>").description("Update a project (name / description / status / settings / metadata only \u2014 workspace and folder cannot be changed via API)").option("--name <name>").option("--description <text>").option("--status <status>").option("--settings <json>").option("--metadata <json>").action(async (id, options) => {
735023
+ await handleProjectsUpdate(id, withGlobals(options));
735024
+ });
735025
+ projects.command("delete <id>").description("Delete a project (drops its sprints and tickets)").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
735026
+ await handleProjectsDelete(id, withGlobals(options));
735027
+ });
735028
+ projects.command("archive <id>").description("Archive a project (transition to `archived` status)").action(async (id, options) => {
735029
+ await handleProjectsArchive(id, withGlobals(options));
735030
+ });
735031
+ projects.command("publish <id>").description("Publish a project (transition from `draft` to `active`)").action(async (id, options) => {
735032
+ await handleProjectsPublish(id, withGlobals(options));
735033
+ });
735034
+ var sprints = program2.command("sprints").description("Manage sprints of a project (requires --project)");
735035
+ sprints.command("ls").alias("list").description("List sprints of a project").requiredOption("--project <id>", "Parent project id").option("--status <status>", "planning | active | completed | failed | pending").option("--page <n>").option("--per-page <n>").action(async (options) => {
735036
+ await handleSprintsList(withGlobals(options));
735037
+ });
735038
+ sprints.command("show <id>").description("Show a sprint (with tickets summary)").requiredOption("--project <id>", "Parent project id").action(async (id, options) => {
735039
+ await handleSprintsShow(id, withGlobals(options));
735040
+ });
735041
+ sprints.command("create").description("Create a sprint").requiredOption("--project <id>", "Parent project id").requiredOption("--name <name>").option("--goal <text>").option("--description <text>").option("--start <YYYY-MM-DD>").option("--end <YYYY-MM-DD>").action(async (options) => {
735042
+ await handleSprintsCreate(withGlobals(options));
735043
+ });
735044
+ sprints.command("update <id>").description("Update a sprint").requiredOption("--project <id>", "Parent project id").option("--name <name>").option("--goal <text>").option("--description <text>").option("--start <YYYY-MM-DD>").option("--end <YYYY-MM-DD>").option("--status <status>").action(async (id, options) => {
735045
+ await handleSprintsUpdate(id, withGlobals(options));
735046
+ });
735047
+ sprints.command("delete <id>").description("Delete a sprint (its tickets return to the backlog)").requiredOption("--project <id>", "Parent project id").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
735048
+ await handleSprintsDelete(id, withGlobals(options));
735049
+ });
735050
+ sprints.command("start <id>").description("Start a sprint (planning \u2192 active)").requiredOption("--project <id>", "Parent project id").action(async (id, options) => {
735051
+ await handleSprintsStart(id, withGlobals(options));
735052
+ });
735053
+ sprints.command("complete <id>").description("Complete a sprint (active \u2192 completed)").requiredOption("--project <id>", "Parent project id").action(async (id, options) => {
735054
+ await handleSprintsComplete(id, withGlobals(options));
735055
+ });
735056
+ var tickets = program2.command("tickets").description("Manage tickets of a project (requires --project)");
735057
+ tickets.command("ls").alias("list").description("List tickets").requiredOption("--project <id>", "Parent project id").option("--sprint <id>", 'Filter by sprint id; use "null" or --backlog for backlog').option("--backlog", "Alias for --sprint null").option("--assignee <id>", 'Filter by assignee id; use "null" for unassigned').option("--status <status>").option("--priority <priority>").option("--label <label>").option("--page <n>").option("--per-page <n>").action(async (options) => {
735058
+ await handleTicketsList(withGlobals(options));
735059
+ });
735060
+ tickets.command("show <id>").description("Show a ticket (with tasks, subtasks, deliverables)").requiredOption("--project <id>", "Parent project id").action(async (id, options) => {
735061
+ await handleTicketsShow(id, withGlobals(options));
735062
+ });
735063
+ tickets.command("create").description("Create a ticket").requiredOption("--project <id>", "Parent project id").requiredOption("--title <text>").option("--description <text>").option("--type <ticket_type>", "task (default) | bug | story | epic | \u2026").option("--sprint <id>", "Attach to a sprint; omit to leave in backlog").option("--assignee <id>", "Assign to a user id").option("--priority <priority>", "lowest | low | medium | high | highest").option("--status <status>").option("--parent <id>", "Parent ticket id (for subtasks)").option("--due <YYYY-MM-DD>").option("--story-points <n>").option("--labels <a,b,c>", "Comma-separated labels").option("--column <column_status>").action(async (options) => {
735064
+ await handleTicketsCreate(withGlobals(options));
735065
+ });
735066
+ tickets.command("update <id>").description("Update a ticket").requiredOption("--project <id>", "Parent project id").option("--title <text>").option("--description <text>").option("--type <ticket_type>").option("--sprint <id>", 'Move to a sprint; use "null" for backlog').option("--assignee <id>", 'Reassign; use "null" to unassign').option("--priority <priority>").option("--status <status>").option("--parent <id>").option("--due <YYYY-MM-DD>").option("--story-points <n>").option("--labels <a,b,c>").option("--column <column_status>").action(async (id, options) => {
735067
+ await handleTicketsUpdate(id, withGlobals(options));
735068
+ });
735069
+ tickets.command("delete <id>").description("Delete a ticket (drops its tasks, deliverables, activities)").requiredOption("--project <id>", "Parent project id").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
735070
+ await handleTicketsDelete(id, withGlobals(options));
735071
+ });
735072
+ tickets.command("assign <id>").description("Assign a ticket to a user; --to null to unassign").requiredOption("--project <id>", "Parent project id").requiredOption("--to <user-id|null>").action(async (id, options) => {
735073
+ await handleTicketsAssign(id, withGlobals(options));
735074
+ });
735075
+ tickets.command("status <id>").description("Change a ticket status (optionally pin the kanban column with --column)").requiredOption("--project <id>", "Parent project id").requiredOption("--to <status>", "open | in_progress | done | blocked | awaiting_human | cancelled | failed").option("--column <column_status>", "Optionally set the kanban column at the same time").action(async (id, options) => {
735076
+ await handleTicketsStatus(id, withGlobals(options));
735077
+ });
735078
+ tickets.command("move <id>").description("Move a ticket to another sprint / column / position").requiredOption("--project <id>", "Parent project id").option("--sprint <id>", 'Target sprint id; use "null" or --backlog for backlog').option("--backlog", "Alias for --sprint null").option("--column <column_status>").option("--position <n>").action(async (id, options) => {
735079
+ await handleTicketsMove(id, withGlobals(options));
735080
+ });
735081
+ tickets.command("activities <id>").description("List activities of a ticket (audit log)").requiredOption("--project <id>", "Parent project id").option("--page <n>").option("--per-page <n>").action(async (id, options) => {
735082
+ await handleTicketsActivities(id, withGlobals(options));
735083
+ });
735084
+ tickets.command("deliverable <id>").description("Publish an existing deliverable already attached to a ticket (see corex#588 for the create-then-publish flow)").requiredOption("--project <id>", "Parent project id").requiredOption("--deliverable <id>", "Deliverable id to publish").action(async (id, options) => {
735085
+ await handleTicketsDeliverable(id, withGlobals(options));
735086
+ });
735087
+ tickets.command("dispatch <id>").description("Dispatch the ticket's already-configured agent (agent_config is read from the ticket, not passed in body)").requiredOption("--project <id>", "Parent project id").action(async (id, options) => {
735088
+ await handleTicketsDispatch(id, withGlobals(options));
735089
+ });
735090
+ var tasks = program2.command("tasks").description("Manage sub-tasks of a ticket (requires --project + --ticket)");
735091
+ tasks.command("ls").alias("list").description("List sub-tasks of a ticket").requiredOption("--project <id>", "Parent project id").requiredOption("--ticket <id>", "Parent ticket id").action(async (options) => {
735092
+ await handleTasksList(withGlobals(options));
735093
+ });
735094
+ tasks.command("show <id>").description("Show a task").requiredOption("--project <id>", "Parent project id").requiredOption("--ticket <id>", "Parent ticket id").action(async (id, options) => {
735095
+ await handleTasksShow(id, withGlobals(options));
735096
+ });
735097
+ tasks.command("create").description("Create a task under a ticket").requiredOption("--project <id>", "Parent project id").requiredOption("--ticket <id>", "Parent ticket id").requiredOption("--title <text>").option("--description <text>").option("--priority <priority>").option("--assignee <id>", 'Assign to a user id; use "null" for unassigned').option("--due <YYYY-MM-DD>").option("--estimate <n>", "Effort estimate (integer)").option("--labels <a,b,c>", "Comma-separated labels").action(async (options) => {
735098
+ await handleTasksCreate(withGlobals(options));
735099
+ });
735100
+ tasks.command("update <id>").description("Update a task").requiredOption("--project <id>", "Parent project id").requiredOption("--ticket <id>", "Parent ticket id").option("--title <text>").option("--description <text>").option("--priority <priority>").option("--assignee <id>").option("--due <YYYY-MM-DD>").option("--estimate <n>").option("--labels <a,b,c>").action(async (id, options) => {
735101
+ await handleTasksUpdate(id, withGlobals(options));
735102
+ });
735103
+ tasks.command("delete <id>").description("Delete a task").requiredOption("--project <id>", "Parent project id").requiredOption("--ticket <id>", "Parent ticket id").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
735104
+ await handleTasksDelete(id, withGlobals(options));
735105
+ });
735106
+ tasks.command("complete <id>").description("Mark a task as completed (or re-open with --uncomplete)").requiredOption("--project <id>", "Parent project id").requiredOption("--ticket <id>", "Parent ticket id").option("--uncomplete", "Re-open the task instead of completing it").action(async (id, options) => {
735107
+ await handleTasksComplete(id, withGlobals(options));
735108
+ });
735109
+ tasks.command("reorder").description("Reorder tasks under a ticket").requiredOption("--project <id>", "Parent project id").requiredOption("--ticket <id>", "Parent ticket id").requiredOption("--order <ids>", "Comma-separated task ids in the desired order").action(async (options) => {
735110
+ await handleTasksReorder(withGlobals(options));
735111
+ });
734253
735112
  program2.parse();
734254
735113
  /*! Bundled license information:
734255
735114
 
package/docs/llm-usage.md CHANGED
@@ -362,6 +362,101 @@ hubdoc permissions ls --on="$WS" --on-type=workspace --json \
362
362
  | jq '.[] | {actor: .actor.display_name, level}'
363
363
  ```
364
364
 
365
+ ### Projects / Sprints / Tickets / Tasks (corex#588)
366
+
367
+ Manage projects (attached to a workspace or standalone), their sprints,
368
+ tickets and sub-tasks — the same domain Synapse agents drive when they
369
+ consume incoming emails, produce deliverables, or hand off work between
370
+ themselves. All commands support `--json` for machine parsing.
371
+
372
+ `hubdoc projects` (top-level: attached to a workspace or standalone) —
373
+ `ls | show | create | update | delete | archive | publish`.
374
+ `--workspace null` filters the standalone (unattached) projects.
375
+
376
+ `hubdoc sprints --project=<pid>` — `ls | show | create | update | delete
377
+ | start | complete`.
378
+
379
+ `hubdoc tickets --project=<pid>` — `ls | show | create | update | delete
380
+ | assign | status | move | activities | deliverable | dispatch`. Every
381
+ verb requires `--project=<pid>`. `ls --backlog` (or `ls --sprint null`)
382
+ lists the backlog. `assign --to null` unassigns. `status --to <state>
383
+ [--column <col>]` uses the canonical statuses: `open | in_progress | done
384
+ | blocked | awaiting_human | cancelled | failed` and optionally pins the
385
+ kanban column at the same time.
386
+
387
+ `hubdoc tasks --project=<pid> --ticket=<tid>` — `ls | show | create |
388
+ update | delete | complete | reorder`. Sub-tasks live under a ticket;
389
+ both parents are required on every call.
390
+
391
+ **Pagination**: all `ls`-shaped verbs return `{ items, pagination:{ page,
392
+ per_page, pages, count, has_more } }` in `--json`. The transport under
393
+ the hood is a bare JSON array plus `X-Total-*` headers (Pagy), same
394
+ convention as `/api/v1/documents`; the CLI normalises so agents don't
395
+ need to touch headers.
396
+
397
+ **Idempotence caveat**: `sprints start` / `sprints complete` follow the
398
+ interactor semantics — re-triggering on a sprint already in the target
399
+ state returns **422** (`unprocessable_entity` with an
400
+ `already_active` / `already_completed` message), not 200.
401
+
402
+ **Deliverables**: `tickets deliverable <id> --deliverable <did>`
403
+ publishes an EXISTING deliverable already attached to the ticket (via
404
+ web UI or a future create endpoint). The API does not yet support
405
+ creating + publishing a deliverable in one CLI call — track corex#588
406
+ follow-ups.
407
+
408
+ **Dispatch**: `tickets dispatch <id>` fires the agent already configured
409
+ on the ticket (`agent_config` is read from the ticket's persisted
410
+ state). To change the agent config, set it on the ticket via the web UI
411
+ or a future `update` extension — the CLI cannot pass it in the request
412
+ body today.
413
+
414
+ #### End-to-end example: agent turns an incoming email into a mission
415
+
416
+ ```bash
417
+ # 0. The Synapse poller has already dropped the raw .eml + attachments
418
+ # into Hubdoc and passed us the email doc id.
419
+ EMAIL_ID="doc-42"
420
+
421
+ # 1. Detach attachments so the agent can classify them separately.
422
+ hubdoc files detach "$EMAIL_ID" --ext pdf,docx --json
423
+
424
+ # 2. Pick the standalone project used for triage (no workspace linkage).
425
+ PID=$(hubdoc projects ls --workspace null --type-id "$TRIAGE_PT_ID" --json \
426
+ | jq -r '.items[0].id')
427
+
428
+ # 3. Create a ticket in the current sprint, priority high, due next week.
429
+ SID=$(hubdoc sprints ls --project "$PID" --status active --json \
430
+ | jq -r '.items[0].id')
431
+ TID=$(hubdoc tickets create --project "$PID" \
432
+ --title "Client X – devis à produire" \
433
+ --sprint "$SID" --assignee "u-1" \
434
+ --priority high --due 2026-07-18 \
435
+ --labels "commercial,urgent" --json | jq -r '.data.id')
436
+
437
+ # 4. Break it down into tasks the agent will chew through.
438
+ hubdoc tasks create --project "$PID" --ticket "$TID" \
439
+ --title "Lire pièces jointes" --priority high --json
440
+ hubdoc tasks create --project "$PID" --ticket "$TID" \
441
+ --title "Rédiger devis" --priority high --due 2026-07-16 --json
442
+
443
+ # 5. Dispatch the agent already configured on the ticket.
444
+ hubdoc tickets dispatch "$TID" --project "$PID" --json
445
+
446
+ # 6. Later — publish an already-attached deliverable and close the ticket.
447
+ hubdoc tickets deliverable "$TID" --project "$PID" \
448
+ --deliverable "$DELIVERABLE_ID" --json
449
+ hubdoc tickets status "$TID" --project "$PID" \
450
+ --to done --column done --json
451
+ ```
452
+
453
+ Common patterns:
454
+ - **Move a ticket to the backlog**: `hubdoc tickets move <id> --project=<pid>
455
+ --backlog` (equivalent to `--sprint null`).
456
+ - **Un-assign**: `hubdoc tickets assign <id> --project=<pid> --to null`.
457
+ - **Reorder a ticket's tasks** (drag-and-drop from the UI equivalent):
458
+ `hubdoc tasks reorder --project=<pid> --ticket=<tid> --order=k3,k1,k2`.
459
+
365
460
  ## Out of scope (planned)
366
461
 
367
462
  - `hubdoc templates create|update|delete` — depends on corex#417 backend
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sinoia/hubdoc-tools",
3
- "version": "1.8.3",
3
+ "version": "1.9.0",
4
4
  "description": "Professional command-line tool for HubDoc document management and bulk import/export",
5
5
  "main": "cli.js",
6
6
  "bin": {