@go-labs-sg/bb 2.48.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -35,31 +35,31 @@ var __toESM = (mod, isNodeMode, target) => {
35
35
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
36
36
  var __require = import.meta.require;
37
37
 
38
- // ../../../node_modules/.bun/dotenv@18.0.6/node_modules/dotenv/dist/index.cjs
38
+ // ../../../node_modules/.bun/dotenv@18.0.7/node_modules/dotenv/dist/index.cjs
39
39
  var require_dist = __commonJS(function(exports, module) {
40
- var S = (e, o) => () => {
40
+ var k = (e, n) => () => {
41
41
  try {
42
- return o || e((o = { exports: {} }).exports, o), o.exports;
42
+ return n || e((n = { exports: {} }).exports, n), n.exports;
43
43
  } catch (t) {
44
- throw o = 0, t;
44
+ throw n = 0, t;
45
45
  }
46
46
  };
47
- var j = S((xe, U) => {
47
+ var I = k((xe, U) => {
48
48
  function G(e) {
49
49
  return typeof e == "string" ? !["false", "0", "no", "off", ""].includes(e.toLowerCase()) : !!e;
50
50
  }
51
- function X(e = process.env) {
52
- let o = {};
51
+ function Y(e = process.env) {
52
+ let n = {};
53
53
  for (let t of ["ENCODING", "PATH", "QUIET", "DEBUG", "OVERRIDE", "FAST"]) {
54
- let n = e[`DOTENV_${t}`] != null ? e[`DOTENV_${t}`] : e[`DOTENV_CONFIG_${t}`];
55
- n != null && (o[t.toLowerCase()] = t === "ENCODING" || t === "PATH" ? n : G(n));
54
+ let o = e[`DOTENV_${t}`] != null ? e[`DOTENV_${t}`] : e[`DOTENV_CONFIG_${t}`];
55
+ o != null && (n[t.toLowerCase()] = t === "ENCODING" || t === "PATH" ? o : G(o));
56
56
  }
57
- return o;
57
+ return n;
58
58
  }
59
- U.exports = { parseBoolean: G, optionsFromEnv: X };
59
+ U.exports = { parseBoolean: G, optionsFromEnv: Y };
60
60
  });
61
- var N = S((ye, x) => {
62
- var Y = __require("fs"), k = __require("path"), z = __require("os"), { URL: Z, fileURLToPath: ee } = __require("url"), { parseBoolean: $, optionsFromEnv: B } = j(), te = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg, b = new Uint8Array(256);
61
+ var N = k((ye, x) => {
62
+ var z = __require("fs"), S = __require("path"), Z = __require("os"), { URL: ee, fileURLToPath: te } = __require("url"), { parseBoolean: $, optionsFromEnv: B } = I(), re = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg, b = new Uint8Array(256);
63
63
  for (let e = 48;e <= 57; e++)
64
64
  b[e] = 1;
65
65
  for (let e = 65;e <= 90; e++)
@@ -69,92 +69,92 @@ var require_dist = __commonJS(function(exports, module) {
69
69
  b[45] = 1;
70
70
  b[46] = 1;
71
71
  b[95] = 1;
72
- function re(e) {
73
- let o = {}, t = e.toString();
72
+ function ne(e) {
73
+ let n = {}, t = e.toString();
74
74
  t = t.replace(/\r\n?/mg, `
75
75
  `);
76
- let n;
77
- for (;(n = te.exec(t)) != null; ) {
78
- let r = n[1], s = n[2] || "";
76
+ let o;
77
+ for (;(o = re.exec(t)) != null; ) {
78
+ let r = o[1], s = o[2] || "";
79
79
  s = s.trim();
80
80
  let i = s[0];
81
81
  s = s.replace(/^(['"`])([\s\S]*)\1$/mg, "$2"), i === '"' && (s = s.replace(/\\n/g, `
82
- `), s = s.replace(/\\r/g, "\r")), o[r] = s;
82
+ `), s = s.replace(/\\r/g, "\r")), n[r] = s;
83
83
  }
84
- return o;
84
+ return n;
85
85
  }
86
86
  function w(e) {
87
87
  return e <= 32 ? e === 32 || e >= 9 && e <= 13 : e >= 160 && (e === 160 || e === 5760 || e >= 8192 && e <= 8202 || e === 8232 || e === 8233 || e === 8239 || e === 8287 || e === 12288 || e === 65279);
88
88
  }
89
- function O(e) {
89
+ function q(e) {
90
90
  return e === 10 || e === 8232 || e === 8233;
91
91
  }
92
92
  function oe(e) {
93
- let o = {}, t = typeof e == "string" ? e : e.toString();
93
+ let n = {}, t = typeof e == "string" ? e : e.toString();
94
94
  t.indexOf("\r") !== -1 && (t = t.replace(/\r\n?/g, `
95
95
  `));
96
- let n = t.length, r = 0;
97
- for (;r < n; ) {
96
+ let o = t.length, r = 0;
97
+ for (;r < o; ) {
98
98
  let s = t.charCodeAt(r);
99
- for (;r < n && w(s); )
99
+ for (;r < o && w(s); )
100
100
  r++, s = t.charCodeAt(r);
101
- if (r >= n)
101
+ if (r >= o)
102
102
  break;
103
103
  if (s === 35) {
104
- for (;r < n && !O(t.charCodeAt(r)); )
104
+ for (;r < o && !q(t.charCodeAt(r)); )
105
105
  r++;
106
106
  continue;
107
107
  }
108
108
  let i = -1;
109
- if (s === 101 && r + 6 < n && t.charCodeAt(r + 1) === 120 && t.charCodeAt(r + 2) === 112 && t.charCodeAt(r + 3) === 111 && t.charCodeAt(r + 4) === 114 && t.charCodeAt(r + 5) === 116) {
109
+ if (s === 101 && r + 6 < o && t.charCodeAt(r + 1) === 120 && t.charCodeAt(r + 2) === 112 && t.charCodeAt(r + 3) === 111 && t.charCodeAt(r + 4) === 114 && t.charCodeAt(r + 5) === 116) {
110
110
  let v = t.charCodeAt(r + 6);
111
111
  if (w(v)) {
112
112
  let u = r + 7;
113
- for (;u < n && w(t.charCodeAt(u)); )
113
+ for (;u < o && w(t.charCodeAt(u)); )
114
114
  u++;
115
115
  b[t.charCodeAt(u)] && (i = r + 6, r = u);
116
116
  } else
117
117
  s = t.charCodeAt(r);
118
118
  }
119
119
  let l = r, d = 0;
120
- for (;r < n && (d = t.charCodeAt(r), b[d]); )
120
+ for (;r < o && (d = t.charCodeAt(r), b[d]); )
121
121
  r++;
122
122
  if (r === l) {
123
- for (;r < n && !O(t.charCodeAt(r)); )
123
+ for (;r < o && !q(t.charCodeAt(r)); )
124
124
  r++;
125
125
  continue;
126
126
  }
127
127
  let p = t.slice(l, r), f = r;
128
- if (r >= n && (d = 0), w(d))
128
+ if (r >= o && (d = 0), w(d))
129
129
  do
130
- r++, d = r < n ? t.charCodeAt(r) : 0;
130
+ r++, d = r < o ? t.charCodeAt(r) : 0;
131
131
  while (w(d));
132
132
  if (d === 61)
133
133
  r++;
134
- else if (d === 58 && r === f && r + 1 < n && w(t.charCodeAt(r + 1)))
134
+ else if (d === 58 && r === f && r + 1 < o && w(t.charCodeAt(r + 1)))
135
135
  r += 2;
136
136
  else {
137
- for (r = i === -1 ? f : i;r < n && !O(t.charCodeAt(r)); )
137
+ for (r = i === -1 ? f : i;r < o && !q(t.charCodeAt(r)); )
138
138
  r++;
139
139
  continue;
140
140
  }
141
141
  let c = r, a = r;
142
- for (;a < n && w(t.charCodeAt(a)); )
142
+ for (;a < o && w(t.charCodeAt(a)); )
143
143
  a++;
144
- let g = t.charCodeAt(a), h, y = false;
145
- if (g === 39 || g === 34 || g === 96) {
144
+ let h = t.charCodeAt(a), g, y = false;
145
+ if (h === 39 || h === 34 || h === 96) {
146
146
  let v = t[a], u = t.indexOf(v, a + 1), m = -1, C = -1;
147
147
  for (;u !== -1; ) {
148
- let q = t.charCodeAt(u - 1) === 92, E = u + 1;
149
- for (;E < n && !O(t.charCodeAt(E)) && w(t.charCodeAt(E)); )
148
+ let O = t.charCodeAt(u - 1) === 92, E = u + 1;
149
+ for (;E < o && !q(t.charCodeAt(E)) && w(t.charCodeAt(E)); )
150
150
  E++;
151
- if ((E === n || O(t.charCodeAt(E)) || t.charCodeAt(E) === 35) && (m = u, C = E), !q)
151
+ if ((E === o || q(t.charCodeAt(E)) || t.charCodeAt(E) === 35) && (m = u, C = E), !O)
152
152
  break;
153
153
  u = t.indexOf(v, u + 1);
154
154
  }
155
155
  if (m !== -1) {
156
- if (h = t.slice(a + 1, m), r = C, t.charCodeAt(r) === 35)
157
- for (;r < n && !O(t.charCodeAt(r)); )
156
+ if (g = t.slice(a + 1, m), r = C, t.charCodeAt(r) === 35)
157
+ for (;r < o && !q(t.charCodeAt(r)); )
158
158
  r++;
159
159
  y = true;
160
160
  }
@@ -162,9 +162,9 @@ var require_dist = __commonJS(function(exports, module) {
162
162
  if (!y) {
163
163
  let v = t.indexOf(`
164
164
  `, c);
165
- v === -1 && (v = n);
165
+ v === -1 && (v = o);
166
166
  let u;
167
- if (n < 4096)
167
+ if (o < 4096)
168
168
  u = t.indexOf("#", c), (u === -1 || u > v) && (u = v);
169
169
  else
170
170
  for (u = c;u < v && t.charCodeAt(u) !== 35; )
@@ -174,38 +174,41 @@ var require_dist = __commonJS(function(exports, module) {
174
174
  m++;
175
175
  for (;C > m && w(t.charCodeAt(C - 1)); )
176
176
  C--;
177
- let q = t.charCodeAt(m);
178
- if (C - m >= 2 && (q === 39 || q === 34 || q === 96) && t.charCodeAt(C - 1) === q ? h = t.slice(m + 1, C - 1) : h = t.slice(m, C), r = u, u < v)
179
- for (;r < n && !O(t.charCodeAt(r)); )
177
+ let O = t.charCodeAt(m);
178
+ if (C - m >= 2 && (O === 39 || O === 34 || O === 96) && t.charCodeAt(C - 1) === O ? g = t.slice(m + 1, C - 1) : g = t.slice(m, C), r = u, u < v)
179
+ for (;r < o && !q(t.charCodeAt(r)); )
180
180
  r++;
181
181
  }
182
- g === 34 && (y || a < r) && h.indexOf("\\") !== -1 && (h = h.replace(/\\n/g, `
183
- `).replace(/\\r/g, "\r")), o[p] = h;
182
+ h === 34 && (y || a < r) && g.indexOf("\\") !== -1 && (g = g.replace(/\\n/g, `
183
+ `).replace(/\\r/g, "\r")), n[p] = g;
184
184
  }
185
- return o;
185
+ return n;
186
186
  }
187
- function ne(e, o) {
188
- return o && $(o.fast) ? oe(e) : re(e);
187
+ function se(e, n) {
188
+ return n && $(n.fast) ? oe(e) : ne(e);
189
189
  }
190
190
  function T(e) {
191
191
  console.log(`\u2506 ${e}`);
192
192
  }
193
- function se(e) {
193
+ function ie(e) {
194
194
  console.error(`\u25C7 ${e}`);
195
195
  }
196
196
  function V(e) {
197
- return e[0] === "~" ? k.join(z.homedir(), e.slice(1)) : e;
197
+ return e[0] === "~" ? S.join(Z.homedir(), e.slice(1)) : e;
198
198
  }
199
- function ie(e = {}) {
200
- return { ...B(), ...e };
199
+ function ce(e = {}) {
200
+ let n = { ...e };
201
+ for (let t of Object.keys(n))
202
+ n[t] === undefined && delete n[t];
203
+ return { ...B(), ...n };
201
204
  }
202
- function ce(e) {
203
- e = ie(e);
204
- let o = k.resolve(process.cwd(), ".env"), t = "utf8", n = process.env;
205
- e && e.processEnv != null && (n = e.processEnv);
205
+ function ae(e) {
206
+ e = ce(e);
207
+ let n = S.resolve(process.cwd(), ".env"), t = "utf8", o = process.env;
208
+ e && e.processEnv != null && (o = e.processEnv);
206
209
  let r = $(e && e.debug);
207
210
  e && e.encoding ? t = e.encoding : r && T("no encoding is specified (UTF-8 is used by default)");
208
- let s = [o];
211
+ let s = [n];
209
212
  if (e && e.path)
210
213
  if (!Array.isArray(e.path))
211
214
  s = [V(e.path)];
@@ -217,106 +220,106 @@ var require_dist = __commonJS(function(exports, module) {
217
220
  let i, l = {}, d = { fast: e.fast };
218
221
  for (let c of s)
219
222
  try {
220
- let a = A.parse(Y.readFileSync(c, { encoding: t }), d);
223
+ let a = A.parse(z.readFileSync(c, { encoding: t }), d);
221
224
  A.populate(l, a, e);
222
225
  } catch (a) {
223
226
  r && T(`failed to load ${c} ${a.message}`), i = a;
224
227
  }
225
- let p = A.populate(n, l, e), f = $(Object.prototype.hasOwnProperty.call(e, "quiet") ? e.quiet : B(n).quiet);
228
+ let p = A.populate(o, l, e), f = $(Object.prototype.hasOwnProperty.call(e, "quiet") ? e.quiet : B(o).quiet);
226
229
  if (r || !f) {
227
230
  let c = Object.keys(p).length, a = [];
228
- for (let g of s)
231
+ for (let h of s)
229
232
  try {
230
- let h = k.relative(process.cwd(), g instanceof Z ? ee(g) : g);
231
- a.push(h);
232
- } catch (h) {
233
- r && T(`failed to load ${g} ${h.message}`), i = h;
233
+ let g = S.relative(process.cwd(), h instanceof ee ? te(h) : h);
234
+ a.push(g);
235
+ } catch (g) {
236
+ r && T(`failed to load ${h} ${g.message}`), i = g;
234
237
  }
235
- se(`injected env (${c}) from ${a.join(",")}`);
238
+ ie(`injected env (${c}) from ${a.join(",")}`);
236
239
  }
237
240
  return i ? { parsed: l, error: i } : { parsed: l };
238
241
  }
239
- function ae(e) {
242
+ function le(e) {
240
243
  return A.configDotenv(e);
241
244
  }
242
- function le(e, o, t = {}) {
243
- let n = $(t && t.debug), r = $(t && t.override), s = {};
244
- if (e === null || typeof e != "object" || o === null || typeof o != "object") {
245
+ function fe(e, n, t = {}) {
246
+ let o = $(t && t.debug), r = $(t && t.override), s = {};
247
+ if (e === null || typeof e != "object" || n === null || typeof n != "object") {
245
248
  let i = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
246
249
  throw i.code = "OBJECT_REQUIRED", i;
247
250
  }
248
- for (let i of Object.keys(o))
249
- Object.prototype.hasOwnProperty.call(e, i) ? (r === true && (e[i] = o[i], s[i] = o[i]), n && T(r === true ? `"${i}" is already defined and WAS overwritten` : `"${i}" is already defined and was NOT overwritten`)) : (e[i] = o[i], s[i] = o[i]);
251
+ for (let i of Object.keys(n))
252
+ Object.prototype.hasOwnProperty.call(e, i) ? (r === true && (e[i] = n[i], s[i] = n[i]), o && T(r === true ? `"${i}" is already defined and WAS overwritten` : `"${i}" is already defined and was NOT overwritten`)) : (e[i] = n[i], s[i] = n[i]);
250
253
  return s;
251
254
  }
252
- var A = { configDotenv: ce, config: ae, parse: ne, populate: le };
255
+ var A = { configDotenv: ae, config: le, parse: se, populate: fe };
253
256
  x.exports.configDotenv = A.configDotenv;
254
257
  x.exports.config = A.config;
255
258
  x.exports.parse = A.parse;
256
259
  x.exports.populate = A.populate;
257
260
  x.exports = A;
258
261
  });
259
- var M = S((Te, W) => {
260
- var _ = __require("child_process"), fe = __require("fs"), L = __require("path");
261
- function ue(e) {
262
- let o = ['"'], t = 0;
263
- for (let n of e) {
264
- if (n === "\\") {
262
+ var M = k((Te, W) => {
263
+ var _ = __require("child_process"), ue = __require("fs"), L = __require("path");
264
+ function de(e) {
265
+ let n = ['"'], t = 0;
266
+ for (let o of e) {
267
+ if (o === "\\") {
265
268
  t++;
266
269
  continue;
267
270
  }
268
- n === '"' ? o.push("\\".repeat(t * 2 + 1), '"') : o.push("\\".repeat(t), n), t = 0;
271
+ o === '"' ? n.push("\\".repeat(t * 2 + 1), '"') : n.push("\\".repeat(t), o), t = 0;
269
272
  }
270
- return o.push("\\".repeat(t * 2), '"'), o.join("");
273
+ return n.push("\\".repeat(t * 2), '"'), n.join("");
271
274
  }
272
- function H(e, o = 1) {
273
- for (let t = 0;t < o; t++) {
274
- let n = [];
275
+ function H(e, n = 1) {
276
+ for (let t = 0;t < n; t++) {
277
+ let o = [];
275
278
  for (let r of e) {
276
279
  let s = r.charCodeAt(0), i = s >= 48 && s <= 57 || s >= 65 && s <= 90 || s >= 97 && s <= 122, l = "\\/:._-".includes(r);
277
- !i && !l && s < 128 && n.push("^"), n.push(r);
280
+ !i && !l && s < 128 && o.push("^"), o.push(r);
278
281
  }
279
- e = n.join("");
282
+ e = o.join("");
280
283
  }
281
284
  return e;
282
285
  }
283
- function P(e, o) {
284
- let t = Object.keys(e).reverse().find((n) => n.toUpperCase() === o);
286
+ function P(e, n) {
287
+ let t = Object.keys(e).reverse().find((o) => o.toUpperCase() === n);
285
288
  return t === undefined ? undefined : e[t];
286
289
  }
287
- function de(e, o, t) {
288
- let n = (P(o, "PATHEXT") || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean), s = n.some((l) => e.toLowerCase().endsWith(l.toLowerCase())) ? ["", ...n] : [...n, ""], i = /[\\/]/.test(e) ? [t] : [t, ...(P(o, "PATH") || "").split(";")];
290
+ function pe(e, n, t) {
291
+ let o = (P(n, "PATHEXT") || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean), s = o.some((l) => e.toLowerCase().endsWith(l.toLowerCase())) ? ["", ...o] : [...o, ""], i = /[\\/]/.test(e) ? [t] : [t, ...(P(n, "PATH") || "").split(";")];
289
292
  for (let l of i)
290
293
  for (let d of s) {
291
294
  let p = L.resolve(t, l.replace(/^"|"$/g, ""), e + d);
292
295
  try {
293
- if (fe.statSync(p).isFile())
296
+ if (ue.statSync(p).isFile())
294
297
  return p;
295
298
  } catch {}
296
299
  }
297
300
  }
298
- function pe(e, o, t) {
301
+ function he(e, n, t) {
299
302
  if (process.platform !== "win32")
300
- return _.spawn(e, o, t);
301
- let n = t.env || process.env, r = de(e, n, t.cwd || process.cwd());
303
+ return _.spawn(e, n, t);
304
+ let o = t.env || process.env, r = pe(e, o, t.cwd || process.cwd());
302
305
  if (r && /\.(?:exe|com)$/i.test(r))
303
- return _.spawn(r, o, t);
306
+ return _.spawn(r, n, t);
304
307
  let s = /\.(?:bat|cmd)$/i.test(r || e), i = [H(L.normalize(r || e))];
305
- for (let d of o)
306
- i.push(H(ue(d), s ? 2 : 1));
308
+ for (let d of n)
309
+ i.push(H(de(d), s ? 2 : 1));
307
310
  let l = i.join(" ");
308
- return _.spawn(P(n, "COMSPEC") || "cmd.exe", ["/d", "/v:off", "/s", "/c", `"${l}"`], { ...t, windowsVerbatimArguments: true });
311
+ return _.spawn(P(o, "COMSPEC") || "cmd.exe", ["/d", "/v:off", "/s", "/c", `"${l}"`], { ...t, windowsVerbatimArguments: true });
309
312
  }
310
- W.exports = pe;
313
+ W.exports = he;
311
314
  });
312
- var K = S(($e, F) => {
313
- var he = __require("fs"), ge = __require("os"), Q = __require("path"), me = __require("child_process"), ve = M(), R = N(), { optionsFromEnv: Ce } = j();
315
+ var X = k(($e, F) => {
316
+ var ge = __require("fs"), me = __require("os"), Q = __require("path"), ve = __require("child_process"), Ce = M(), R = N(), { optionsFromEnv: J } = I();
314
317
  function D() {
315
318
  console.log(["Usage: dotenv run [--help] [-q|--quiet] [--debug] [--override] [--fast] [-f|--file <paths>] [--] <command> [args...]", "", "Run a command with environment variables from a .env file.", "Place dotenv options before the command; all following arguments go to the command.", "", "Options:", " -f, --file <paths> .env paths, comma-separated or repeated (default: .env)", " -q, --quiet suppress the injected env message", " --debug enable debug logging", " --override override existing environment variables", " --fast use the faster character-scanner parser", "", "Environment variables (DOTENV_CONFIG_* names remain as fallbacks):", " DOTENV_PATH, DOTENV_ENCODING, DOTENV_QUIET,", " DOTENV_DEBUG, DOTENV_OVERRIDE,", " DOTENV_FAST"].join(`
316
319
  `));
317
320
  }
318
321
  function we(e) {
319
- let o = [], t = false, n, r, s, i, l = -1;
322
+ let n = [], t = false, o, r, s, i, l = -1;
320
323
  for (let p = 0;p < e.length; p++) {
321
324
  let f = e[p];
322
325
  if (f === "--") {
@@ -326,7 +329,7 @@ var require_dist = __commonJS(function(exports, module) {
326
329
  if (f === "--help" || f === "-h")
327
330
  return { help: true };
328
331
  if (f === "--quiet" || f === "-q") {
329
- n = true;
332
+ o = true;
330
333
  continue;
331
334
  }
332
335
  if (f === "--debug") {
@@ -342,13 +345,13 @@ var require_dist = __commonJS(function(exports, module) {
342
345
  continue;
343
346
  }
344
347
  if (f === "-f" || f === "--file" || f.startsWith("-f=") || f.startsWith("--file=")) {
345
- let c = f.indexOf("="), a = c === -1 ? f : f.slice(0, c), g = c === -1 ? e[++p] : f.slice(c + 1);
346
- if (!g || g === "--")
348
+ let c = f.indexOf("="), a = c === -1 ? f : f.slice(0, c), h = c === -1 ? e[++p] : f.slice(c + 1);
349
+ if (!h || h === "--")
347
350
  return { error: `${a} requires a path` };
348
- let h = g.split(",").map((y) => y.trim()).filter(Boolean);
349
- if (h.length === 0)
351
+ let g = h.split(",").map((y) => y.trim()).filter(Boolean);
352
+ if (g.length === 0)
350
353
  return { error: `${a} requires a path` };
351
- o.push(...h), t = true;
354
+ n.push(...g), t = true;
352
355
  continue;
353
356
  }
354
357
  if (f.startsWith("-"))
@@ -357,36 +360,36 @@ var require_dist = __commonJS(function(exports, module) {
357
360
  break;
358
361
  }
359
362
  let d = l === -1 ? [] : e.slice(l);
360
- return { paths: o, pathSet: t, quiet: n, debug: r, override: s, fast: i, command: d };
363
+ return { paths: n, pathSet: t, quiet: o, debug: r, override: s, fast: i, command: d };
361
364
  }
362
365
  function Ae(e) {
363
- return e[0] === "~" ? Q.join(ge.homedir(), e.slice(1)) : e;
366
+ return e[0] === "~" ? Q.join(me.homedir(), e.slice(1)) : e;
364
367
  }
365
368
  function Ee(e) {
366
- let o = Ce(), t = { encoding: o.encoding || "utf8", quiet: o.quiet === true, debug: o.debug === true, override: o.override === true, fast: o.fast === true, paths: [".env"], defaultPath: true };
367
- return o.path != null && (t.paths = [o.path], t.defaultPath = false), e.pathSet && (t.paths = e.paths, t.defaultPath = false), e.quiet != null && (t.quiet = e.quiet), e.debug != null && (t.debug = e.debug), e.override != null && (t.override = e.override), e.fast != null && (t.fast = e.fast), t;
369
+ let n = J(), t = { encoding: n.encoding || "utf8", quiet: n.quiet, debug: n.debug === true, override: n.override === true, fast: n.fast === true, paths: [".env"], defaultPath: true };
370
+ return n.path != null && (t.paths = [n.path], t.defaultPath = false), e.pathSet && (t.paths = e.paths, t.defaultPath = false), e.quiet != null && (t.quiet = e.quiet), e.debug != null && (t.debug = e.debug), e.override != null && (t.override = e.override), e.fast != null && (t.fast = e.fast), t;
368
371
  }
369
372
  function be(e) {
370
- let o = {}, t = [], n = { override: e.override, debug: e.debug };
373
+ let n = {}, t = [], o = { override: e.override, debug: e.debug };
371
374
  for (let s of e.paths) {
372
375
  let i = Q.resolve(process.cwd(), Ae(s));
373
376
  try {
374
- let l = R.parse(he.readFileSync(i, { encoding: e.encoding }), { fast: e.fast });
375
- R.populate(o, l, n), t.push(s);
377
+ let l = R.parse(ge.readFileSync(i, { encoding: e.encoding }), { fast: e.fast });
378
+ R.populate(n, l, o), t.push(s);
376
379
  } catch (l) {
377
380
  if (e.debug && console.log(`\u2506 failed to load ${s} ${l.message}`), !(e.defaultPath && l.code === "ENOENT"))
378
381
  throw l;
379
382
  }
380
383
  }
381
- return { injected: R.populate(process.env, o, n), loadedPaths: t };
384
+ return { injected: R.populate(process.env, n, o), loadedPaths: t };
382
385
  }
383
- function J(e) {
384
- let o = e[0];
385
- if (o === "--help" || o === "-h") {
386
+ function K(e) {
387
+ let n = e[0];
388
+ if (n === "--help" || n === "-h") {
386
389
  D();
387
390
  return;
388
391
  }
389
- if (o !== "run") {
392
+ if (n !== "run") {
390
393
  D(), process.exitCode = 1;
391
394
  return;
392
395
  }
@@ -403,22 +406,22 @@ var require_dist = __commonJS(function(exports, module) {
403
406
  D(), process.exitCode = 1;
404
407
  return;
405
408
  }
406
- let n = Ee(t);
409
+ let o = Ee(t);
407
410
  try {
408
- let c = be(n);
409
- if (!n.quiet) {
410
- let a = `\u25C7 injected env (${Object.keys(c.injected).length})`;
411
- c.loadedPaths.length > 0 && (a += ` from ${c.loadedPaths.join(", ")}`), console.error(a);
411
+ let c = be(o);
412
+ if (!(o.quiet == null ? J().quiet : o.quiet)) {
413
+ let h = `\u25C7 injected env (${Object.keys(c.injected).length})`;
414
+ c.loadedPaths.length > 0 && (h += ` from ${c.loadedPaths.join(", ")}`), console.error(h);
412
415
  }
413
416
  } catch (c) {
414
417
  console.error(`dotenv: ${c.message}`), process.exitCode = 1;
415
418
  return;
416
419
  }
417
- let r = !!process.stdin.isTTY, s = process.platform !== "win32" && !r, i = ve(t.command[0], t.command.slice(1), { stdio: "inherit", detached: s }), l = new Map, d = 0;
420
+ let r = !!process.stdin.isTTY, s = process.platform !== "win32" && !r, i = Ce(t.command[0], t.command.slice(1), { stdio: "inherit", detached: s }), l = new Map, d = 0;
418
421
  function p(c) {
419
422
  if (!(!i.pid || i.exitCode !== null || i.signalCode !== null)) {
420
423
  if (process.platform === "win32") {
421
- me.spawnSync("taskkill", ["/pid", String(i.pid), "/T", "/F"], { stdio: "ignore" });
424
+ ve.spawnSync("taskkill", ["/pid", String(i.pid), "/T", "/F"], { stdio: "ignore" });
422
425
  return;
423
426
  }
424
427
  try {
@@ -453,16 +456,16 @@ var require_dist = __commonJS(function(exports, module) {
453
456
  f(), typeof c == "number" ? process.exit(c) : (setInterval(() => {}, 1000), process.kill(process.pid, a));
454
457
  });
455
458
  }
456
- F.exports = J;
457
- F === __require.main && J(process.argv.slice(2));
459
+ F.exports = K;
460
+ F === __require.main && K(process.argv.slice(2));
458
461
  });
459
- var I = N();
460
- var Oe = K();
461
- module.exports = I;
462
- module.exports.config = I.config;
463
- module.exports.configDotenv = I.configDotenv;
464
- module.exports.parse = I.parse;
465
- module.exports.populate = I.populate;
462
+ var j = N();
463
+ var qe = X();
464
+ module.exports = j;
465
+ module.exports.config = j.config;
466
+ module.exports.configDotenv = j.configDotenv;
467
+ module.exports.parse = j.parse;
468
+ module.exports.populate = j.populate;
466
469
  });
467
470
 
468
471
  // src/load-env.ts
@@ -6716,6 +6719,155 @@ var logoutCliSession = async ({
6716
6719
  // src/cli.ts
6717
6720
  import { readFileSync } from "fs";
6718
6721
 
6722
+ // ../utils/src/data-table-sort-keys.ts
6723
+ var INVENTORY_SORT_IDS = [
6724
+ "itemCategory",
6725
+ "category",
6726
+ "supplier",
6727
+ "cost",
6728
+ "name",
6729
+ "createdAt",
6730
+ "updatedAt"
6731
+ ];
6732
+ var ITEM_CATEGORY_SORT_IDS = [
6733
+ "name",
6734
+ "itemCount",
6735
+ "createdAt",
6736
+ "createdBy"
6737
+ ];
6738
+
6739
+ // ../db/src/generated/prisma/enums.ts
6740
+ var EstimationMode = {
6741
+ MICRO: "MICRO",
6742
+ MANPOWER_SPECIFIED: "MANPOWER_SPECIFIED"
6743
+ };
6744
+ var ProjectStatus = {
6745
+ PITCH: "PITCH",
6746
+ WON: "WON",
6747
+ COMPLETED: "COMPLETED",
6748
+ RECONCILED: "RECONCILED",
6749
+ LOST: "LOST"
6750
+ };
6751
+ var BudgetStatus = {
6752
+ DRAFT: "DRAFT",
6753
+ PENDING_APPROVAL: "PENDING_APPROVAL",
6754
+ APPROVED: "APPROVED",
6755
+ REJECTED: "REJECTED",
6756
+ ESTIMATE_CREATED: "ESTIMATE_CREATED",
6757
+ ESTIMATE_SENT: "ESTIMATE_SENT",
6758
+ ESTIMATE_ACCEPTED: "ESTIMATE_ACCEPTED",
6759
+ ESTIMATE_REJECTED: "ESTIMATE_REJECTED",
6760
+ ESTIMATE_CLOSED: "ESTIMATE_CLOSED"
6761
+ };
6762
+ var ApprovalStatus = {
6763
+ PENDING_APPROVAL: "PENDING_APPROVAL",
6764
+ APPROVED: "APPROVED",
6765
+ REJECTED: "REJECTED",
6766
+ SUPERSEDED: "SUPERSEDED"
6767
+ };
6768
+ var ApprovalType = {
6769
+ BUDGET: "BUDGET",
6770
+ SUPPLIER: "SUPPLIER",
6771
+ BILL: "BILL",
6772
+ CUSTOMER_INVOICE: "CUSTOMER_INVOICE",
6773
+ QUOTATION: "QUOTATION"
6774
+ };
6775
+ var ErrorSeverity = {
6776
+ LOW: "LOW",
6777
+ MEDIUM: "MEDIUM",
6778
+ HIGH: "HIGH",
6779
+ CRITICAL: "CRITICAL"
6780
+ };
6781
+ var ErrorStatus = {
6782
+ UNRESOLVED: "UNRESOLVED",
6783
+ INVESTIGATING: "INVESTIGATING",
6784
+ RESOLVED: "RESOLVED",
6785
+ IGNORED: "IGNORED"
6786
+ };
6787
+ var CustomerInvoiceBatchStatus = {
6788
+ CREATING: "CREATING",
6789
+ PENDING_APPROVAL: "PENDING_APPROVAL",
6790
+ APPROVED: "APPROVED",
6791
+ REJECTED_VOIDED: "REJECTED_VOIDED",
6792
+ EXPIRED_VOIDED: "EXPIRED_VOIDED",
6793
+ PARTIAL_QBO_FAILURE: "PARTIAL_QBO_FAILURE"
6794
+ };
6795
+ var CustomerInvoiceStatus = {
6796
+ CREATED_IN_QBO: "CREATED_IN_QBO",
6797
+ APPROVED: "APPROVED",
6798
+ SENT: "SENT",
6799
+ PAID: "PAID",
6800
+ VOIDED: "VOIDED",
6801
+ QBO_SYNC_ERROR: "QBO_SYNC_ERROR"
6802
+ };
6803
+ var BillStatus = {
6804
+ DRAFT: "DRAFT",
6805
+ PENDING_APPROVAL: "PENDING_APPROVAL",
6806
+ CHECKED: "CHECKED",
6807
+ APPROVED: "APPROVED",
6808
+ PAID: "PAID",
6809
+ REJECTED: "REJECTED"
6810
+ };
6811
+
6812
+ // ../utils/src/filter-enums.ts
6813
+ var ExtendedApprovalStatus = {
6814
+ ...ApprovalStatus,
6815
+ ALL: "ALL"
6816
+ };
6817
+ var ExtendedApprovalType = {
6818
+ ...ApprovalType,
6819
+ ALL: "ALL"
6820
+ };
6821
+ var ExtendedProjectStatus = {
6822
+ ...ProjectStatus,
6823
+ ALL: "ALL"
6824
+ };
6825
+ var ExtendedBudgetStatus = {
6826
+ ...BudgetStatus,
6827
+ ALL: "ALL"
6828
+ };
6829
+ var ExtendedBillStatus = {
6830
+ ...BillStatus,
6831
+ ALL: "ALL"
6832
+ };
6833
+ var ExtendedEstimationMode = {
6834
+ ...EstimationMode,
6835
+ ALL: "ALL"
6836
+ };
6837
+ var BudgetRole = {
6838
+ ALL: "ALL",
6839
+ BD: "BD",
6840
+ CREATOR: "CREATOR",
6841
+ INSIDE_SALES: "INSIDE_SALES"
6842
+ };
6843
+ var Deals = {
6844
+ ALL: "ALL",
6845
+ SUCCESSFUL: "SUCCESSFUL",
6846
+ LOST: "LOST"
6847
+ };
6848
+ var TimeFrame = {
6849
+ ALL: "ALL",
6850
+ LAST_YEAR: "LAST_YEAR",
6851
+ LAST_6_MONTHS: "LAST_6_MONTHS",
6852
+ LAST_3_MONTHS: "LAST_3_MONTHS",
6853
+ CUSTOM: "CUSTOM"
6854
+ };
6855
+ var ChartTimeFrame = {
6856
+ LAST_30_DAYS: "last_30_days",
6857
+ LAST_3_MONTHS: "last_3_months",
6858
+ LAST_6_MONTHS: "last_6_months",
6859
+ LAST_1_YEAR: "last_1_year",
6860
+ ALL_TIME: "all_time"
6861
+ };
6862
+ var ExtendedErrorSeverity = {
6863
+ ALL: "ALL",
6864
+ ...ErrorSeverity
6865
+ };
6866
+ var ExtendedErrorStatus = {
6867
+ ALL: "ALL",
6868
+ ...ErrorStatus
6869
+ };
6870
+
6719
6871
  // src/cli-trace.ts
6720
6872
  var cliQuietSession = false;
6721
6873
  var setCliQuiet = (quiet) => {
@@ -6737,6 +6889,7 @@ var SENSITIVE_FLAG = new Set([
6737
6889
  "authorization"
6738
6890
  ]);
6739
6891
  var SENSITIVE_TRACE_FIELD = new Set([
6892
+ "secret",
6740
6893
  "paymentproof",
6741
6894
  "paymentproofattachments",
6742
6895
  "paymentproofpath",
@@ -6754,6 +6907,14 @@ var sanitizeValueForTrace = (value, seen = new WeakSet) => {
6754
6907
  if (Array.isArray(value)) {
6755
6908
  return value.map((item) => sanitizeValueForTrace(item, seen));
6756
6909
  }
6910
+ if (value instanceof Error) {
6911
+ return sanitizeValueForTrace({
6912
+ ...value,
6913
+ name: value.name,
6914
+ message: value.message,
6915
+ stack: value.stack
6916
+ }, seen);
6917
+ }
6757
6918
  const prototype = Object.getPrototypeOf(value);
6758
6919
  if (prototype !== Object.prototype && prototype !== null)
6759
6920
  return value;
@@ -6776,9 +6937,8 @@ var sanitizeFlagsForTrace = (flags) => {
6776
6937
  };
6777
6938
  var consumeCliQuietFlags = (flags) => {
6778
6939
  const fromEnv = process.env.BB_CLI_QUIET === "1";
6779
- const fromFlags = flags.quiet === true || flags.q === true;
6940
+ const fromFlags = flags.quiet === true;
6780
6941
  delete flags.quiet;
6781
- delete flags.q;
6782
6942
  return fromEnv || fromFlags;
6783
6943
  };
6784
6944
  var logCliAction = (record) => {
@@ -6864,7 +7024,7 @@ var api2 = new Proxy({}, {
6864
7024
  });
6865
7025
 
6866
7026
  // src/prisma-enums.ts
6867
- var ProjectStatus = {
7027
+ var ProjectStatus2 = {
6868
7028
  PITCH: "PITCH",
6869
7029
  WON: "WON",
6870
7030
  COMPLETED: "COMPLETED",
@@ -6878,7 +7038,7 @@ var SupplierQuotationStatus = {
6878
7038
  REJECTED: "REJECTED",
6879
7039
  SUPERSEDED: "SUPERSEDED"
6880
7040
  };
6881
- var BudgetStatus = {
7041
+ var BudgetStatus2 = {
6882
7042
  DRAFT: "DRAFT",
6883
7043
  PENDING_APPROVAL: "PENDING_APPROVAL",
6884
7044
  APPROVED: "APPROVED",
@@ -6889,13 +7049,13 @@ var BudgetStatus = {
6889
7049
  ESTIMATE_REJECTED: "ESTIMATE_REJECTED",
6890
7050
  ESTIMATE_CLOSED: "ESTIMATE_CLOSED"
6891
7051
  };
6892
- var ApprovalStatus = {
7052
+ var ApprovalStatus2 = {
6893
7053
  PENDING_APPROVAL: "PENDING_APPROVAL",
6894
7054
  APPROVED: "APPROVED",
6895
7055
  REJECTED: "REJECTED",
6896
7056
  SUPERSEDED: "SUPERSEDED"
6897
7057
  };
6898
- var ApprovalType = {
7058
+ var ApprovalType2 = {
6899
7059
  BUDGET: "BUDGET",
6900
7060
  SUPPLIER: "SUPPLIER",
6901
7061
  BILL: "BILL",
@@ -6909,7 +7069,7 @@ var UserRole = {
6909
7069
  INSIDE_SALES: "INSIDE_SALES",
6910
7070
  ACCOUNTING_TEAM: "ACCOUNTING_TEAM"
6911
7071
  };
6912
- var BillStatus = {
7072
+ var BillStatus2 = {
6913
7073
  DRAFT: "DRAFT",
6914
7074
  PENDING_APPROVAL: "PENDING_APPROVAL",
6915
7075
  CHECKED: "CHECKED",
@@ -6950,222 +7110,97 @@ import { readFile as readFile2, writeFile } from "fs/promises";
6950
7110
  import { basename } from "path";
6951
7111
  import { createInterface as createInterface2 } from "readline/promises";
6952
7112
 
6953
- // ../db/src/generated/prisma/enums.ts
6954
- var EstimationMode = {
6955
- MICRO: "MICRO",
6956
- MANPOWER_SPECIFIED: "MANPOWER_SPECIFIED"
7113
+ // src/runtime/error.ts
7114
+ var invocationErrorCodes = new Set([
7115
+ "USAGE",
7116
+ "VALIDATION",
7117
+ "CONFIRMATION_REQUIRED"
7118
+ ]);
7119
+ var trpcCodeMap = {
7120
+ BAD_REQUEST: "VALIDATION",
7121
+ CONFLICT: "CONFLICT",
7122
+ FORBIDDEN: "AUTHORIZATION",
7123
+ INTERNAL_SERVER_ERROR: "API",
7124
+ METHOD_NOT_SUPPORTED: "API",
7125
+ NOT_FOUND: "NOT_FOUND",
7126
+ PARSE_ERROR: "VALIDATION",
7127
+ PAYLOAD_TOO_LARGE: "VALIDATION",
7128
+ PRECONDITION_FAILED: "CONFLICT",
7129
+ TIMEOUT: "NETWORK",
7130
+ TOO_MANY_REQUESTS: "API",
7131
+ UNAUTHORIZED: "AUTHENTICATION",
7132
+ UNPROCESSABLE_CONTENT: "VALIDATION"
6957
7133
  };
6958
- var ProjectStatus2 = {
6959
- PITCH: "PITCH",
6960
- WON: "WON",
6961
- COMPLETED: "COMPLETED",
6962
- RECONCILED: "RECONCILED",
6963
- LOST: "LOST"
7134
+ var cliErrorCodes = new Set([
7135
+ "USAGE",
7136
+ "VALIDATION",
7137
+ "CONFIRMATION_REQUIRED",
7138
+ "AUTHENTICATION",
7139
+ "AUTHORIZATION",
7140
+ "NOT_FOUND",
7141
+ "CONFLICT",
7142
+ "API",
7143
+ "NETWORK",
7144
+ "ABORTED",
7145
+ "INTERRUPTED",
7146
+ "INTERNAL"
7147
+ ]);
7148
+
7149
+ class CliRuntimeError extends Error {
7150
+ code;
7151
+ details;
7152
+ requestId;
7153
+ constructor(code, message, options = {}) {
7154
+ super(message);
7155
+ this.name = "CliRuntimeError";
7156
+ this.code = code;
7157
+ this.details = options.details;
7158
+ this.requestId = options.requestId;
7159
+ }
7160
+ }
7161
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7162
+ var asNonEmptyString = (value) => typeof value === "string" && value.trim() !== "" ? value : undefined;
7163
+ var formatValidationMessage = (message, data) => {
7164
+ const zodError = isRecord(data?.zodError) ? data.zodError : undefined;
7165
+ const fieldErrors = isRecord(zodError?.fieldErrors) ? zodError.fieldErrors : undefined;
7166
+ const fields = fieldErrors ? Object.entries(fieldErrors).flatMap(([field, errors]) => Array.isArray(errors) ? errors.filter((entry) => typeof entry === "string").map((entry) => `${field}: ${entry}`) : []) : [];
7167
+ const formErrors = Array.isArray(zodError?.formErrors) ? zodError.formErrors.filter((entry) => typeof entry === "string") : [];
7168
+ const validationIssues = [...fields, ...formErrors];
7169
+ if (validationIssues.length > 0)
7170
+ return validationIssues.join("; ");
7171
+ let parsed = message;
7172
+ for (let depth = 0;depth < 3 && typeof parsed === "string"; depth++) {
7173
+ try {
7174
+ parsed = JSON.parse(parsed);
7175
+ } catch {
7176
+ return message;
7177
+ }
7178
+ }
7179
+ if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every(isRecord))
7180
+ return message;
7181
+ const issues = parsed.map((issue) => {
7182
+ if (!Array.isArray(issue.path) || typeof issue.message !== "string" || typeof issue.code !== "string")
7183
+ return;
7184
+ const path = issue.path.filter((part) => typeof part === "string" || typeof part === "number").join(".") || "payload";
7185
+ if (issue.code === "invalid_type" && typeof issue.expected === "string" && issue.message.toLowerCase().includes("required")) {
7186
+ return `${path} must be a ${issue.expected}${path === "cost" ? ', e.g. "19"' : ""}`;
7187
+ }
7188
+ if (issue.code === "invalid_format" && issue.format === "regex" && path === "name" && String(issue.pattern ?? issue.message).includes("A-Za-z0-9,.?@&!#'~* _;+-")) {
7189
+ return `${path} contains characters that are not allowed. Allowed: letters, numbers, spaces and , . ? @ & ! # ' ~ * _ ; + -`;
7190
+ }
7191
+ return `${path}: ${issue.message}`;
7192
+ });
7193
+ return issues.every((issue) => issue !== undefined) ? issues.join("; ") : message;
6964
7194
  };
6965
- var BudgetStatus2 = {
6966
- DRAFT: "DRAFT",
6967
- PENDING_APPROVAL: "PENDING_APPROVAL",
6968
- APPROVED: "APPROVED",
6969
- REJECTED: "REJECTED",
6970
- ESTIMATE_CREATED: "ESTIMATE_CREATED",
6971
- ESTIMATE_SENT: "ESTIMATE_SENT",
6972
- ESTIMATE_ACCEPTED: "ESTIMATE_ACCEPTED",
6973
- ESTIMATE_REJECTED: "ESTIMATE_REJECTED",
6974
- ESTIMATE_CLOSED: "ESTIMATE_CLOSED"
6975
- };
6976
- var ApprovalStatus2 = {
6977
- PENDING_APPROVAL: "PENDING_APPROVAL",
6978
- APPROVED: "APPROVED",
6979
- REJECTED: "REJECTED",
6980
- SUPERSEDED: "SUPERSEDED"
6981
- };
6982
- var ApprovalType2 = {
6983
- BUDGET: "BUDGET",
6984
- SUPPLIER: "SUPPLIER",
6985
- BILL: "BILL",
6986
- CUSTOMER_INVOICE: "CUSTOMER_INVOICE",
6987
- QUOTATION: "QUOTATION"
6988
- };
6989
- var ErrorSeverity = {
6990
- LOW: "LOW",
6991
- MEDIUM: "MEDIUM",
6992
- HIGH: "HIGH",
6993
- CRITICAL: "CRITICAL"
6994
- };
6995
- var ErrorStatus = {
6996
- UNRESOLVED: "UNRESOLVED",
6997
- INVESTIGATING: "INVESTIGATING",
6998
- RESOLVED: "RESOLVED",
6999
- IGNORED: "IGNORED"
7000
- };
7001
- var CustomerInvoiceBatchStatus = {
7002
- CREATING: "CREATING",
7003
- PENDING_APPROVAL: "PENDING_APPROVAL",
7004
- APPROVED: "APPROVED",
7005
- REJECTED_VOIDED: "REJECTED_VOIDED",
7006
- EXPIRED_VOIDED: "EXPIRED_VOIDED",
7007
- PARTIAL_QBO_FAILURE: "PARTIAL_QBO_FAILURE"
7008
- };
7009
- var CustomerInvoiceStatus = {
7010
- CREATED_IN_QBO: "CREATED_IN_QBO",
7011
- APPROVED: "APPROVED",
7012
- SENT: "SENT",
7013
- PAID: "PAID",
7014
- VOIDED: "VOIDED",
7015
- QBO_SYNC_ERROR: "QBO_SYNC_ERROR"
7016
- };
7017
- var BillStatus2 = {
7018
- DRAFT: "DRAFT",
7019
- PENDING_APPROVAL: "PENDING_APPROVAL",
7020
- CHECKED: "CHECKED",
7021
- APPROVED: "APPROVED",
7022
- PAID: "PAID",
7023
- REJECTED: "REJECTED"
7024
- };
7025
-
7026
- // ../utils/src/filter-enums.ts
7027
- var ExtendedApprovalStatus = {
7028
- ...ApprovalStatus2,
7029
- ALL: "ALL"
7030
- };
7031
- var ExtendedApprovalType = {
7032
- ...ApprovalType2,
7033
- ALL: "ALL"
7034
- };
7035
- var ExtendedProjectStatus = {
7036
- ...ProjectStatus2,
7037
- ALL: "ALL"
7038
- };
7039
- var ExtendedBudgetStatus = {
7040
- ...BudgetStatus2,
7041
- ALL: "ALL"
7042
- };
7043
- var ExtendedBillStatus = {
7044
- ...BillStatus2,
7045
- ALL: "ALL"
7046
- };
7047
- var ExtendedEstimationMode = {
7048
- ...EstimationMode,
7049
- ALL: "ALL"
7050
- };
7051
- var BudgetRole = {
7052
- ALL: "ALL",
7053
- BD: "BD",
7054
- CREATOR: "CREATOR",
7055
- INSIDE_SALES: "INSIDE_SALES"
7056
- };
7057
- var Deals = {
7058
- ALL: "ALL",
7059
- SUCCESSFUL: "SUCCESSFUL",
7060
- LOST: "LOST"
7061
- };
7062
- var TimeFrame = {
7063
- ALL: "ALL",
7064
- LAST_YEAR: "LAST_YEAR",
7065
- LAST_6_MONTHS: "LAST_6_MONTHS",
7066
- LAST_3_MONTHS: "LAST_3_MONTHS",
7067
- CUSTOM: "CUSTOM"
7068
- };
7069
- var ExtendedErrorSeverity = {
7070
- ALL: "ALL",
7071
- ...ErrorSeverity
7072
- };
7073
- var ExtendedErrorStatus = {
7074
- ALL: "ALL",
7075
- ...ErrorStatus
7076
- };
7077
-
7078
- // src/runtime/error.ts
7079
- var invocationErrorCodes = new Set([
7080
- "USAGE",
7081
- "VALIDATION",
7082
- "CONFIRMATION_REQUIRED"
7083
- ]);
7084
- var trpcCodeMap = {
7085
- BAD_REQUEST: "VALIDATION",
7086
- CONFLICT: "CONFLICT",
7087
- FORBIDDEN: "AUTHORIZATION",
7088
- INTERNAL_SERVER_ERROR: "API",
7089
- METHOD_NOT_SUPPORTED: "API",
7090
- NOT_FOUND: "NOT_FOUND",
7091
- PARSE_ERROR: "VALIDATION",
7092
- PAYLOAD_TOO_LARGE: "VALIDATION",
7093
- PRECONDITION_FAILED: "CONFLICT",
7094
- TIMEOUT: "NETWORK",
7095
- TOO_MANY_REQUESTS: "API",
7096
- UNAUTHORIZED: "AUTHENTICATION",
7097
- UNPROCESSABLE_CONTENT: "VALIDATION"
7098
- };
7099
- var cliErrorCodes = new Set([
7100
- "USAGE",
7101
- "VALIDATION",
7102
- "CONFIRMATION_REQUIRED",
7103
- "AUTHENTICATION",
7104
- "AUTHORIZATION",
7105
- "NOT_FOUND",
7106
- "CONFLICT",
7107
- "API",
7108
- "NETWORK",
7109
- "ABORTED",
7110
- "INTERRUPTED",
7111
- "INTERNAL"
7112
- ]);
7113
-
7114
- class CliRuntimeError extends Error {
7115
- code;
7116
- details;
7117
- requestId;
7118
- constructor(code, message, options = {}) {
7119
- super(message);
7120
- this.name = "CliRuntimeError";
7121
- this.code = code;
7122
- this.details = options.details;
7123
- this.requestId = options.requestId;
7124
- }
7125
- }
7126
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7127
- var asNonEmptyString = (value) => typeof value === "string" && value.trim() !== "" ? value : undefined;
7128
- var formatValidationMessage = (message, data) => {
7129
- const zodError = isRecord(data?.zodError) ? data.zodError : undefined;
7130
- const fieldErrors = isRecord(zodError?.fieldErrors) ? zodError.fieldErrors : undefined;
7131
- const fields = fieldErrors ? Object.entries(fieldErrors).flatMap(([field, errors]) => Array.isArray(errors) ? errors.filter((entry) => typeof entry === "string").map((entry) => `${field}: ${entry}`) : []) : [];
7132
- const formErrors = Array.isArray(zodError?.formErrors) ? zodError.formErrors.filter((entry) => typeof entry === "string") : [];
7133
- const validationIssues = [...fields, ...formErrors];
7134
- if (validationIssues.length > 0)
7135
- return validationIssues.join("; ");
7136
- let parsed = message;
7137
- for (let depth = 0;depth < 3 && typeof parsed === "string"; depth++) {
7138
- try {
7139
- parsed = JSON.parse(parsed);
7140
- } catch {
7141
- return message;
7142
- }
7143
- }
7144
- if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every(isRecord))
7145
- return message;
7146
- const issues = parsed.map((issue) => {
7147
- if (!Array.isArray(issue.path) || typeof issue.message !== "string" || typeof issue.code !== "string")
7148
- return;
7149
- const path = issue.path.filter((part) => typeof part === "string" || typeof part === "number").join(".") || "payload";
7150
- if (issue.code === "invalid_type" && typeof issue.expected === "string" && issue.message.toLowerCase().includes("required")) {
7151
- return `${path} must be a ${issue.expected}${path === "cost" ? ', e.g. "19"' : ""}`;
7152
- }
7153
- if (issue.code === "invalid_format" && issue.format === "regex" && path === "name" && String(issue.pattern ?? issue.message).includes("A-Za-z0-9,.?@&!#'~* _;+-")) {
7154
- return `${path} contains characters that are not allowed. Allowed: letters, numbers, spaces and , . ? @ & ! # ' ~ * _ ; + -`;
7155
- }
7156
- return `${path}: ${issue.message}`;
7157
- });
7158
- return issues.every((issue) => issue !== undefined) ? issues.join("; ") : message;
7159
- };
7160
- var asDetails = (value) => {
7161
- if (!isRecord(value))
7162
- return;
7163
- const details = {};
7164
- for (const [key, nestedValue] of Object.entries(value)) {
7165
- if (isJsonValue(nestedValue))
7166
- details[key] = nestedValue;
7167
- }
7168
- return Object.keys(details).length > 0 ? details : undefined;
7195
+ var asDetails = (value) => {
7196
+ if (!isRecord(value))
7197
+ return;
7198
+ const details = {};
7199
+ for (const [key, nestedValue] of Object.entries(value)) {
7200
+ if (isJsonValue(nestedValue))
7201
+ details[key] = nestedValue;
7202
+ }
7203
+ return Object.keys(details).length > 0 ? details : undefined;
7169
7204
  };
7170
7205
  var isJsonValue = (value) => {
7171
7206
  if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
@@ -7277,9 +7312,9 @@ var customerInvoiceLifecycleStatuses = [
7277
7312
  ];
7278
7313
 
7279
7314
  // src/parse-cli-enums.ts
7280
- var BUDGET_STATUS_VALUES = new Set(Object.values(BudgetStatus));
7281
- var BILL_STATUS_VALUES = new Set(Object.values(BillStatus));
7282
- var PROJECT_STATUS_VALUES = new Set(Object.values(ProjectStatus));
7315
+ var BUDGET_STATUS_VALUES = new Set(Object.values(BudgetStatus2));
7316
+ var BILL_STATUS_VALUES = new Set(Object.values(BillStatus2));
7317
+ var PROJECT_STATUS_VALUES = new Set(Object.values(ProjectStatus2));
7283
7318
  var SUPPLIER_QUOTATION_STATUS_VALUES = new Set(Object.values(SupplierQuotationStatus));
7284
7319
  var CUSTOMER_INVOICE_LIFECYCLE_STATUS_VALUES = new Set(customerInvoiceLifecycleStatuses);
7285
7320
  var USER_ROLE_VALUES = new Set(Object.values(UserRole));
@@ -7326,7 +7361,7 @@ function parseBillStatusForUpdate(raw) {
7326
7361
  if (!BILL_STATUS_VALUES.has(raw)) {
7327
7362
  throw new Error(`Invalid bill status "${raw}". Use one of: ${[...BILL_STATUS_VALUES].join(", ")}.`);
7328
7363
  }
7329
- if (raw === BillStatus.PENDING_APPROVAL) {
7364
+ if (raw === BillStatus2.PENDING_APPROVAL) {
7330
7365
  throw new Error("Cannot set PENDING_APPROVAL via update-bill-status. Use create-bill-approval <billId> instead.");
7331
7366
  }
7332
7367
  return raw;
@@ -7373,6 +7408,16 @@ var PROJECT_HUB_SORT_FIELDS = new Set([
7373
7408
  "companyName",
7374
7409
  "status"
7375
7410
  ]);
7411
+ function parseOptionalIsoDateFlag(raw, flagName, { endOfDay = false } = {}) {
7412
+ if (raw === undefined || raw === "")
7413
+ return;
7414
+ const value = /^\d{4}-\d{2}-\d{2}$/.test(raw) ? `${raw}T${endOfDay ? "23:59:59.999" : "00:00:00.000"}Z` : /T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(raw) ? `${raw}Z` : raw;
7415
+ const parsed = new Date(value);
7416
+ if (Number.isNaN(parsed.getTime())) {
7417
+ throw new Error(`${flagName} must be an ISO date (YYYY-MM-DD) or ISO datetime.`);
7418
+ }
7419
+ return parsed;
7420
+ }
7376
7421
  function parseOptionalProjectHubSortBy(raw) {
7377
7422
  if (raw === undefined || raw === "")
7378
7423
  return;
@@ -7565,10 +7610,10 @@ function parseApprovalTypeFlag(raw) {
7565
7610
  }
7566
7611
  return u;
7567
7612
  }
7568
- var budgetStatusesForHelp = Object.values(BudgetStatus);
7569
- var billStatusesForHelp = Object.values(BillStatus);
7570
- var billStatusesForUpdateHelp = Object.values(BillStatus).filter((s) => s !== BillStatus.PENDING_APPROVAL);
7571
- var projectStatusesForHelp = Object.values(ProjectStatus);
7613
+ var budgetStatusesForHelp = Object.values(BudgetStatus2);
7614
+ var billStatusesForHelp = Object.values(BillStatus2);
7615
+ var billStatusesForUpdateHelp = Object.values(BillStatus2).filter((s) => s !== BillStatus2.PENDING_APPROVAL);
7616
+ var projectStatusesForHelp = Object.values(ProjectStatus2);
7572
7617
  var userRolesForHelp = Object.values(UserRole);
7573
7618
 
7574
7619
  // ../utils/src/rich-text.ts
@@ -8494,11 +8539,11 @@ var confirmCurrentCommand = async (input) => {
8494
8539
  };
8495
8540
  // src/commands/index.ts
8496
8541
  import { contentType } from "mime-types";
8497
- var BUDGET = ApprovalType.BUDGET;
8498
- var BILL = ApprovalType.BILL;
8499
- var SUPPLIER = ApprovalType.SUPPLIER;
8500
- var QUOTATION = ApprovalType.QUOTATION;
8501
- var CUSTOMER_INVOICE = ApprovalType.CUSTOMER_INVOICE;
8542
+ var BUDGET = ApprovalType2.BUDGET;
8543
+ var BILL = ApprovalType2.BILL;
8544
+ var SUPPLIER = ApprovalType2.SUPPLIER;
8545
+ var QUOTATION = ApprovalType2.QUOTATION;
8546
+ var CUSTOMER_INVOICE = ApprovalType2.CUSTOMER_INVOICE;
8502
8547
  var ASANA_WON_LOST_SECTION_GIDS = new Set([
8503
8548
  "1211678338364908",
8504
8549
  "1211678338364907"
@@ -8555,7 +8600,7 @@ var createApprovalEmailDeliveryFailure = (error) => ({
8555
8600
  error
8556
8601
  });
8557
8602
  function isPendingRequest(n) {
8558
- return n.notificationType === "request" && n.status === ApprovalStatus.PENDING_APPROVAL;
8603
+ return n.notificationType === "request" && n.status === ApprovalStatus2.PENDING_APPROVAL;
8559
8604
  }
8560
8605
  var out = (data) => {
8561
8606
  emitCommandResult(data);
@@ -8762,11 +8807,11 @@ var quotationAttachmentContentTypeForFileName = (fileName) => {
8762
8807
  throw new Error(`Unsupported quotation attachment type: ${fileName}. Use PDF, JPEG, or PNG.`);
8763
8808
  };
8764
8809
  var LOCKED_BUDGET_STATUSES_REQUIRING_CONFIRMATION = new Set([
8765
- BudgetStatus.ESTIMATE_CREATED,
8766
- BudgetStatus.ESTIMATE_SENT,
8767
- BudgetStatus.ESTIMATE_ACCEPTED,
8768
- BudgetStatus.ESTIMATE_REJECTED,
8769
- BudgetStatus.ESTIMATE_CLOSED
8810
+ BudgetStatus2.ESTIMATE_CREATED,
8811
+ BudgetStatus2.ESTIMATE_SENT,
8812
+ BudgetStatus2.ESTIMATE_ACCEPTED,
8813
+ BudgetStatus2.ESTIMATE_REJECTED,
8814
+ BudgetStatus2.ESTIMATE_CLOSED
8770
8815
  ]);
8771
8816
  var lockedBudgetStatusLabels = {
8772
8817
  ESTIMATE_CREATED: "Estimate Created",
@@ -8888,36 +8933,20 @@ var updateProjectWithEstimateInvalidationConfirmation = async (input) => {
8888
8933
  });
8889
8934
  }
8890
8935
  };
8891
- async function listBudgets(opts) {
8892
- const input = {
8893
- projectId: opts.projectId,
8894
- name: opts.name,
8895
- statuses: opts.statuses,
8896
- createdBy: opts.createdBy,
8897
- dateFrom: opts.dateFrom,
8898
- dateTo: opts.dateTo,
8899
- active: opts.active,
8900
- sortBy: opts.sortBy,
8901
- sortDir: opts.sortDir,
8902
- page: opts.page ?? 1,
8903
- perPage: opts.perPage ?? 20,
8904
- includeDetails: opts.includeDetails ?? true
8905
- };
8906
- const result = await api2.budget.getAllBudgets.query(input);
8907
- out(result);
8908
- }
8936
+ var listBudgets = async (input) => {
8937
+ out(await api2.budget.getAllBudgets.query(input));
8938
+ };
8939
+ var countBudgets = async (input) => {
8940
+ out(await api2.budget.getCounts.query(input));
8941
+ };
8909
8942
  async function getBudget(id, opts) {
8910
- const [budget, items] = await Promise.all([
8911
- api2.budget.getBudget.query({
8912
- id,
8913
- includeInactive: opts?.includeInactive ?? false
8914
- }),
8915
- api2.budget.getBudgetItems.query({
8916
- budgetId: id,
8917
- includeInactive: opts?.includeInactive ?? false
8918
- })
8943
+ const includeInactive = opts?.includeInactive ?? false;
8944
+ const [budget, items, totals] = await Promise.all([
8945
+ api2.budget.getBudget.query({ id, includeInactive }),
8946
+ api2.budget.getBudgetItems.query({ budgetId: id, includeInactive }),
8947
+ api2.budget.getBudgetItemTotals.query({ budgetId: id, includeInactive })
8919
8948
  ]);
8920
- out({ budget, items });
8949
+ out({ budget, items: { ...items, totals } });
8921
8950
  }
8922
8951
  async function updateBudgetStatus(budgetId, status, opts) {
8923
8952
  await assertSensitiveWorkflowConfirmed({
@@ -8944,7 +8973,7 @@ async function updateBudgetStatus(budgetId, status, opts) {
8944
8973
  });
8945
8974
  out({
8946
8975
  ...result,
8947
- invoiceSetup: result.newStatus === BudgetStatus.ESTIMATE_ACCEPTED && result.hasQuickbooksEstimate ? invoiceSplitSetupNextAction(budgetId) : null
8976
+ invoiceSetup: result.newStatus === BudgetStatus2.ESTIMATE_ACCEPTED && result.hasQuickbooksEstimate ? invoiceSplitSetupNextAction(budgetId) : null
8948
8977
  });
8949
8978
  }
8950
8979
  var assertBudgetActionReadiness = async ({
@@ -9011,22 +9040,22 @@ var runBudgetPreflight = async (budgetId, preflight) => {
9011
9040
  await updateProjectWithEstimateInvalidationConfirmation(payload);
9012
9041
  }
9013
9042
  };
9014
- async function listBills(opts) {
9015
- const input = {
9016
- projectId: opts.projectId,
9017
- budgetId: opts.budgetId,
9018
- statuses: billStatusesForApi(opts.statuses),
9019
- search: opts.search?.trim() || undefined,
9020
- isClaimable: opts.isClaimable,
9021
- createdByIds: opts.createdByIds && opts.createdByIds.length > 0 ? opts.createdByIds : undefined,
9022
- sortBy: opts.sortBy,
9023
- sortDir: opts.sortDir,
9024
- page: opts.page ?? 1,
9025
- pageSize: opts.pageSize ?? 20
9026
- };
9027
- const result = await api2.bill.getAll.query(input);
9028
- out(result);
9029
- }
9043
+ var listBills = async (input) => {
9044
+ out(await api2.bill.getAll.query({
9045
+ ...input,
9046
+ statuses: billStatusesForApi(input.statuses),
9047
+ search: input.search?.trim() || undefined,
9048
+ createdByIds: input.createdByIds?.length ? input.createdByIds : undefined
9049
+ }));
9050
+ };
9051
+ var countBills = async (input) => {
9052
+ out(await api2.bill.getCounts.query({
9053
+ ...input,
9054
+ statuses: billStatusesForApi(input.statuses),
9055
+ search: input.search?.trim() || undefined,
9056
+ createdByIds: input.createdByIds?.length ? input.createdByIds : undefined
9057
+ }));
9058
+ };
9030
9059
  async function approveBill(billId, overBudgetOverrideReason) {
9031
9060
  await assertSensitiveWorkflowConfirmed({
9032
9061
  action: "Approve bill",
@@ -9041,7 +9070,7 @@ async function approveBill(billId, overBudgetOverrideReason) {
9041
9070
  }
9042
9071
  const result = await api2.bill.updateApproval.mutate({
9043
9072
  billApprovalId: approval.id,
9044
- status: ApprovalStatus.APPROVED,
9073
+ status: ApprovalStatus2.APPROVED,
9045
9074
  overBudgetOverrideReason
9046
9075
  });
9047
9076
  if (result.processing) {
@@ -9098,20 +9127,12 @@ async function listApprovals(opts) {
9098
9127
  });
9099
9128
  out({ items });
9100
9129
  }
9101
- async function listSuppliers(opts) {
9102
- const result = await api2.supplier.getSuppliers.query({
9103
- page: opts.page ?? 1,
9104
- perPage: opts.perPage ?? 10,
9105
- sort: opts.sort ?? [{ id: "createdAt", desc: true }],
9106
- name: opts.name,
9107
- createdBy: opts.createdBy,
9108
- gstRegistered: opts.gstRegistered ?? null,
9109
- status: opts.status,
9110
- supplierTags: opts.supplierTags,
9111
- active: opts.active ?? true
9112
- });
9113
- out(result);
9114
- }
9130
+ var listSuppliers = async (opts) => {
9131
+ out(await api2.supplier.getSuppliers.query(opts));
9132
+ };
9133
+ var countSuppliers = async (opts) => {
9134
+ out(await api2.supplier.getSummary.query(opts));
9135
+ };
9115
9136
  async function createBillApproval(billId) {
9116
9137
  await assertSensitiveWorkflowConfirmed({
9117
9138
  action: "Create bill approval request",
@@ -9137,10 +9158,10 @@ async function createBillApproval(billId) {
9137
9158
  });
9138
9159
  }
9139
9160
  var validateBillStatusUpdateOptions = (opts) => {
9140
- if (opts.status === BillStatus.PAID && !opts.paymentReference?.trim()) {
9161
+ if (opts.status === BillStatus2.PAID && !opts.paymentReference?.trim()) {
9141
9162
  throw new Error("update-bill-status requires --paymentReference when status is PAID.");
9142
9163
  }
9143
- if (opts.status !== BillStatus.PAID && opts.paymentProofPath !== undefined) {
9164
+ if (opts.status !== BillStatus2.PAID && opts.paymentProofPath !== undefined) {
9144
9165
  throw new Error("--paymentProof can only be used when status is PAID.");
9145
9166
  }
9146
9167
  if (opts.paymentProofPath !== undefined && !opts.paymentProofPath.trim()) {
@@ -9148,13 +9169,10 @@ var validateBillStatusUpdateOptions = (opts) => {
9148
9169
  }
9149
9170
  };
9150
9171
  var deleteStagedPaymentProof = async (projectId, key) => {
9151
- const cleanup = await api2.attachment.deleteStagedBillAttachments.mutate({
9172
+ await api2.attachment.deleteStagedBillAttachments.mutate({
9152
9173
  projectId,
9153
9174
  keys: [key]
9154
9175
  });
9155
- if (cleanup.failedKeys.includes(key)) {
9156
- throw new Error(`Failed to delete staged payment proof ${key}.`);
9157
- }
9158
9176
  };
9159
9177
  var rethrowAfterStagedPaymentProofCleanup = async (error, cleanup) => {
9160
9178
  if (cleanup) {
@@ -9288,10 +9306,9 @@ async function patchBillInvoiceNumber(id, invoiceNumber) {
9288
9306
  });
9289
9307
  out(result);
9290
9308
  }
9291
- async function getBillAttachments(id) {
9292
- const result = await api2.bill.getAttachments.query({ id });
9293
- out(result);
9294
- }
9309
+ var getBillAttachments = async (input) => {
9310
+ out(await api2.bill.getAttachments.query(input));
9311
+ };
9295
9312
  async function stageBillAttachmentsFromPaths(projectId, filePaths) {
9296
9313
  const attachments = [];
9297
9314
  for (const filePath of filePaths) {
@@ -9499,7 +9516,7 @@ async function uploadBudgetWinProofFromPath(budgetId, filePath, opts) {
9499
9516
  });
9500
9517
  const result = await api2.budget.updateBudgetStatus.mutate({
9501
9518
  id: budgetId,
9502
- status: BudgetStatus.ESTIMATE_ACCEPTED,
9519
+ status: BudgetStatus2.ESTIMATE_ACCEPTED,
9503
9520
  ...opts?.projectManagerId !== undefined && {
9504
9521
  projectManagerId: opts.projectManagerId
9505
9522
  },
@@ -9523,11 +9540,12 @@ async function getBillDetails(id) {
9523
9540
  out(result);
9524
9541
  }
9525
9542
  async function getBudgetItemsOnly(budgetId, opts) {
9526
- const items = await api2.budget.getBudgetItems.query({
9527
- budgetId,
9528
- includeInactive: opts?.includeInactive ?? false
9529
- });
9530
- out(items);
9543
+ const includeInactive = opts?.includeInactive ?? false;
9544
+ const [items, totals] = await Promise.all([
9545
+ api2.budget.getBudgetItems.query({ budgetId, includeInactive }),
9546
+ api2.budget.getBudgetItemTotals.query({ budgetId, includeInactive })
9547
+ ]);
9548
+ out({ ...items, totals });
9531
9549
  }
9532
9550
  async function addBudgetItems(opts) {
9533
9551
  await confirmLockedBudgetChangeByBudgetId(opts.budgetId, "Add budget items");
@@ -9564,10 +9582,9 @@ var approveUnavailableItemException = async (input) => {
9564
9582
  const result = await api2.budgetItem.approveUnavailableItemException.mutate(input);
9565
9583
  out(result);
9566
9584
  };
9567
- async function getBudgetCategories() {
9568
- const categories = await api2.budget.getBudgetCategories.query();
9569
- out(categories);
9570
- }
9585
+ var getBudgetCategories = async (input) => {
9586
+ out(await api2.budget.searchBudgetCategories.query(input));
9587
+ };
9571
9588
  var getBudgetVersions = async (budgetId, options = {}) => {
9572
9589
  if (options.all && options.cursor !== undefined) {
9573
9590
  throw new CliRuntimeError("USAGE", "--all cannot be combined with --cursor.");
@@ -9581,15 +9598,15 @@ var getBudgetVersions = async (budgetId, options = {}) => {
9581
9598
  limit: options.limit,
9582
9599
  cursor
9583
9600
  });
9584
- versions.push(...page.versions);
9585
- cursor = page.nextCursor;
9586
- if (!options.all || cursor === undefined)
9601
+ versions.push(...page.items);
9602
+ cursor = page.pageInfo.nextCursor;
9603
+ if (!options.all || cursor === null)
9587
9604
  break;
9588
9605
  if (seenCursors.has(cursor)) {
9589
9606
  throw new Error("Budget history pagination did not advance; export aborted.");
9590
9607
  }
9591
9608
  seenCursors.add(cursor);
9592
- } while (cursor !== undefined);
9609
+ } while (cursor !== null);
9593
9610
  out({ versions, nextCursor: cursor ?? null });
9594
9611
  };
9595
9612
  async function createBudgetApproval(budgetId, preflight) {
@@ -9840,10 +9857,12 @@ async function createBillFromPayload(raw, draftOnlyFlag, quotationExempt = false
9840
9857
  }) : await api2.bill.create.mutate(input);
9841
9858
  out(result);
9842
9859
  }
9843
- async function listQuotations(opts) {
9844
- const result = await api2.quotation.list.query(opts);
9845
- out(result);
9846
- }
9860
+ var listQuotations = async (input) => {
9861
+ out(await api2.quotation.listPage.query(input));
9862
+ };
9863
+ var countQuotations = async (input) => {
9864
+ out(await api2.quotation.getCounts.query(input));
9865
+ };
9847
9866
  async function getQuotationDetails(id) {
9848
9867
  const result = await api2.quotation.getById.query({ id });
9849
9868
  out(result);
@@ -9888,8 +9907,10 @@ var checkCustomerInvoiceReadiness = async (budgetId) => {
9888
9907
  out(result);
9889
9908
  };
9890
9909
  var listCustomerInvoices = async (input) => {
9891
- const result = await api2.customerInvoice.list.query(input);
9892
- out(result);
9910
+ out(await api2.customerInvoice.list.query(input));
9911
+ };
9912
+ var countCustomerInvoices = async (input) => {
9913
+ out(await api2.customerInvoice.getCounts.query(input));
9893
9914
  };
9894
9915
  var listEligibleCustomerInvoiceBudgets = async (projectId) => {
9895
9916
  const result = await api2.customerInvoice.getEligibleBudgets.query({
@@ -9945,7 +9966,7 @@ var createCustomerInvoice = async (raw) => {
9945
9966
  await assertSensitiveWorkflowConfirmed({
9946
9967
  action: "Create customer invoice",
9947
9968
  entity: `budget ${input.budgetId}`,
9948
- details: `creates ${input.splits.length} invoice(s) in QuickBooks from the budget's remaining uninvoiced balance: ${input.splits.map((split) => `${split.label} (${split.percentage}% of the estimate), due ${new Date(split.dueDate).toISOString().slice(0, 10)}`).join("; ")}. Admin creation is auto-approved; other creators request approval. No customer email is sent.`
9969
+ details: `queues ${input.splits.length} invoice(s) for creation in QuickBooks from the budget's remaining uninvoiced balance: ${input.splits.map((split) => `${split.label} (${split.percentage}% of the estimate), due ${new Date(split.dueDate).toISOString().slice(0, 10)}`).join("; ")}. Admin creation is auto-approved; other creators request approval. No customer email is sent.`
9949
9970
  });
9950
9971
  const result = await api2.customerInvoice.createInvoiceBatch.mutate(input);
9951
9972
  out(result);
@@ -9965,7 +9986,7 @@ var deleteCustomerInvoice = async (batchId) => {
9965
9986
  await assertSensitiveWorkflowConfirmed({
9966
9987
  action: "Delete customer invoice",
9967
9988
  entity: `invoice batch ${batchId}`,
9968
- details: "deletes its invoices from QuickBooks and removes the local batch; paid invoices are blocked until their QBO payments are reversed; removing approved coverage may reopen the estimate"
9989
+ details: "queues deletion of its invoices in QuickBooks; the local batch is removed once QuickBooks confirms; paid invoices are blocked until their QBO payments are reversed; removing approved coverage may reopen the estimate"
9969
9990
  });
9970
9991
  const result = await api2.customerInvoice.deleteInvoiceBatch.mutate({
9971
9992
  batchId
@@ -9976,7 +9997,7 @@ var voidCustomerInvoice = async (batchId) => {
9976
9997
  await assertSensitiveWorkflowConfirmed({
9977
9998
  action: "Void customer invoice",
9978
9999
  entity: `invoice batch ${batchId}`,
9979
- details: "voids its invoices in QuickBooks and updates Budget Builder; paid invoices are blocked until their QBO payments are reversed; removing approved coverage may reopen the estimate"
10000
+ details: "queues a void of its invoices in QuickBooks; Budget Builder updates once QuickBooks confirms; paid invoices are blocked until their QBO payments are reversed; removing approved coverage may reopen the estimate"
9980
10001
  });
9981
10002
  const result = await api2.customerInvoice.voidInvoiceBatch.mutate({ batchId });
9982
10003
  out(result);
@@ -9996,7 +10017,7 @@ var rejectCustomerInvoice = async (batchId, rejectionReason) => {
9996
10017
  await assertSensitiveWorkflowConfirmed({
9997
10018
  action: "Reject customer invoice",
9998
10019
  entity: `invoice batch ${batchId}`,
9999
- details: `records the rejection reason, voids it in QuickBooks, notifies its creator, and reopens a prematurely closed estimate when applicable`
10020
+ details: `queues a void of its invoices in QuickBooks; once QuickBooks confirms, records the rejection reason, notifies its creator, and reopens a prematurely closed estimate when applicable`
10000
10021
  });
10001
10022
  const result = await api2.customerInvoice.rejectInvoiceBatch.mutate({
10002
10023
  batchId,
@@ -10080,7 +10101,7 @@ async function createSupplierFromPayload(raw) {
10080
10101
  throw new CliRuntimeError(normalized.code, `Supplier creation outcome is uncertain for ${JSON.stringify(input.name)}. Run list-suppliers and inspect matching supplier details before retrying; names may not be unique, so do not retry create-supplier until you can verify that no supplier was created. Original error: ${normalized.message}`);
10081
10102
  }
10082
10103
  const supplier = created.result;
10083
- if (supplier.status !== ApprovalStatus.PENDING_APPROVAL) {
10104
+ if (supplier.status !== ApprovalStatus2.PENDING_APPROVAL) {
10084
10105
  out(created);
10085
10106
  return;
10086
10107
  }
@@ -10147,6 +10168,10 @@ async function updateProjectFromPayload(raw) {
10147
10168
  const result = await updateProjectWithEstimateInvalidationConfirmation(input);
10148
10169
  out(result);
10149
10170
  }
10171
+ var getCompanyQuickBooksSyncStatus = async (companyId) => {
10172
+ const result = await api2.company.getQuickBooksSyncStatus.query({ companyId });
10173
+ out(result);
10174
+ };
10150
10175
  async function updateCompanyFromPayload(raw) {
10151
10176
  const input = parseCompanyUpdatePayload(raw);
10152
10177
  const result = await api2.company.updateCompany.mutate(input);
@@ -10398,16 +10423,14 @@ async function rejectQuotation(quotationId, reason) {
10398
10423
  });
10399
10424
  out(result);
10400
10425
  }
10401
- async function listCompanies(opts) {
10402
- const result = await api2.company.getCompanies.query({
10403
- name: opts.name,
10404
- createdBy: opts.createdBy,
10405
- active: opts.active,
10406
- page: opts.page ?? 1,
10407
- perPage: opts.perPage ?? 20
10408
- });
10426
+ var listCompanies = async (input) => {
10427
+ const result = await api2.company.getCompanies.query(input);
10409
10428
  out(result);
10410
- }
10429
+ };
10430
+ var countCompanies = async (input) => {
10431
+ const result = await api2.company.getCompanyCounts.query(input);
10432
+ out(result);
10433
+ };
10411
10434
  async function getCompany(id) {
10412
10435
  const company = await api2.company.getCompanyById.query({ id });
10413
10436
  out(company);
@@ -10416,31 +10439,26 @@ async function createCompany(opts) {
10416
10439
  const result = await api2.company.createCompany.mutate(opts);
10417
10440
  out(result);
10418
10441
  }
10419
- var searchExistingCompanies = async (query, page = 1) => {
10420
- out(await api2.company.searchExistingCompanies.query({ query, page }));
10442
+ var searchExistingCompanies = async (input) => {
10443
+ out(await api2.company.searchExistingCompanies.query(input));
10421
10444
  };
10422
10445
  var reuseExistingCompany = async (input) => {
10423
10446
  out(await api2.company.useExistingCompany.mutate(input));
10424
10447
  };
10425
- async function listProjects(opts) {
10426
- const result = await api2.project.getAllProjects.query({
10427
- companyId: opts.companyId,
10428
- name: opts.name,
10429
- status: opts.status,
10430
- active: opts.active,
10431
- page: opts.page ?? 1,
10432
- perPage: opts.perPage ?? 20
10433
- });
10448
+ var listProjects = async (input) => {
10449
+ const result = await api2.project.getAllProjects.query(input);
10434
10450
  out(result);
10435
- }
10436
- var listProjectHubs = async (opts) => {
10437
- const result = await api2.project.getAllProjectHubs.query({
10438
- ...opts,
10439
- page: opts.page ?? 1,
10440
- perPage: opts.perPage ?? 20
10441
- });
10451
+ };
10452
+ var countProjects = async (input) => {
10453
+ const result = await api2.project.getProjectCounts.query(input);
10442
10454
  out(result);
10443
10455
  };
10456
+ var listProjectHubs = async (input) => {
10457
+ out(await api2.project.getAllProjectHubs.query(input));
10458
+ };
10459
+ var countProjectHubs = async (input) => {
10460
+ out(await api2.project.getProjectHubCounts.query(input));
10461
+ };
10444
10462
  async function getProject(id) {
10445
10463
  const [project, budgetsOverview, billsOverview] = await Promise.all([
10446
10464
  api2.project.getProjectDetailsById.query({ id }),
@@ -10536,7 +10554,7 @@ var reconcileProject = async (projectId) => {
10536
10554
  out(result);
10537
10555
  };
10538
10556
  var completeProject = async (projectId) => {
10539
- await updateProjectStatus(projectId, ProjectStatus.COMPLETED);
10557
+ await updateProjectStatus(projectId, ProjectStatus2.COMPLETED);
10540
10558
  };
10541
10559
  var importQuickBooksProjectId = async (projectId, projectUrl) => {
10542
10560
  const result = await api2.project.importQboProjectId.mutate({
@@ -10545,14 +10563,71 @@ var importQuickBooksProjectId = async (projectId, projectUrl) => {
10545
10563
  });
10546
10564
  out(result);
10547
10565
  };
10548
- var listIntegrationOperations = async (input) => {
10549
- const result = await api2.integration.listOperations.query(input);
10550
- out(result);
10566
+ var listWebhookEventTypes = async () => {
10567
+ out(await api2.webhook.listEventTypes.query());
10551
10568
  };
10552
- var retryIntegrationOperation = async (operationId, confirmExternalStateReconciled, outboundEmailResolution, providerMessageId) => {
10553
- await assertSensitiveWorkflowConfirmed({
10554
- action: outboundEmailResolution === "ACCEPTED" ? "Reconcile integration operation as completed" : "Retry integration operation",
10555
- entity: `operation ${operationId}`,
10569
+ var listWebhookEndpoints = async () => {
10570
+ out(await api2.webhook.listEndpoints.query());
10571
+ };
10572
+ var getWebhookEndpoint = async (id) => {
10573
+ out(await api2.webhook.getEndpoint.query({ id }));
10574
+ };
10575
+ var createWebhookEndpoint = async (input) => {
10576
+ const result = await api2.webhook.createEndpoint.mutate(input);
10577
+ out({
10578
+ ...result,
10579
+ note: "Store the signing secret now; Budget Builder never shows it again."
10580
+ });
10581
+ };
10582
+ var updateWebhookEndpoint = async (input) => {
10583
+ out(await api2.webhook.updateEndpoint.mutate(input));
10584
+ };
10585
+ var deleteWebhookEndpoint = async (id) => {
10586
+ await assertSensitiveWorkflowConfirmed({
10587
+ action: "Delete webhook endpoint and its delivery history",
10588
+ entity: `webhook endpoint ${id}`
10589
+ });
10590
+ out(await api2.webhook.deleteEndpoint.mutate({ id }));
10591
+ };
10592
+ var rotateWebhookSecret = async (input) => {
10593
+ await assertSensitiveWorkflowConfirmed({
10594
+ action: "Rotate webhook signing secret",
10595
+ entity: `webhook endpoint ${input.id}`,
10596
+ details: `previous secrets keep signing for ${input.previousSecretTtlHours ?? 24}h`
10597
+ });
10598
+ const result = await api2.webhook.rotateSecret.mutate(input);
10599
+ out({
10600
+ ...result,
10601
+ note: "Store the new signing secret now; Budget Builder never shows it again."
10602
+ });
10603
+ };
10604
+ var sendWebhookTestEvent = async (id) => {
10605
+ out(await api2.webhook.sendTestEvent.mutate({ id }));
10606
+ };
10607
+ var listWebhookDeliveries = async (input) => {
10608
+ out(await api2.webhook.listDeliveries.query(input));
10609
+ };
10610
+ var getWebhookDelivery = async (deliveryId) => {
10611
+ out(await api2.webhook.getDelivery.query({ deliveryId }));
10612
+ };
10613
+ var replayWebhookDelivery = async (deliveryId) => {
10614
+ out(await api2.webhook.replayDelivery.mutate({ deliveryId }));
10615
+ };
10616
+ var listIntegrationOperations = async (input) => {
10617
+ const result = await api2.integration.listOperations.query(input);
10618
+ out(result);
10619
+ };
10620
+ var getIntegrationOperation = async (operationId) => {
10621
+ const result = await api2.integration.getOperation.query({ operationId });
10622
+ out(result);
10623
+ };
10624
+ var countIntegrationOperations = async (input) => {
10625
+ out(await api2.integration.getCounts.query(input));
10626
+ };
10627
+ var retryIntegrationOperation = async (operationId, confirmExternalStateReconciled, outboundEmailResolution, providerMessageId) => {
10628
+ await assertSensitiveWorkflowConfirmed({
10629
+ action: outboundEmailResolution === "ACCEPTED" ? "Reconcile integration operation as completed" : "Retry integration operation (QuickBooks jobs with a saved attempt check QuickBooks first and never resend an applied change)",
10630
+ entity: `operation ${operationId}`,
10556
10631
  details: outboundEmailResolution === "ACCEPTED" ? `mark provider-accepted email sent with message ID ${providerMessageId ?? "<missing>"}` : outboundEmailResolution === "NOT_ACCEPTED_RETRY" ? "rotate the Resend idempotency key and retry provider delivery" : confirmExternalStateReconciled ? "after confirming the external state has been reconciled" : undefined
10557
10632
  });
10558
10633
  const result = await api2.integration.retryOperation.mutate({
@@ -10563,10 +10638,25 @@ var retryIntegrationOperation = async (operationId, confirmExternalStateReconcil
10563
10638
  });
10564
10639
  out(result);
10565
10640
  };
10641
+ var reconcileResourceOperation = async (operationId) => {
10642
+ await assertSensitiveWorkflowConfirmed({
10643
+ action: "Verify QuickBooks resource outcome",
10644
+ entity: `operation ${operationId}`,
10645
+ details: "verify the saved receipt or read QuickBooks when the attempt is uncertain, then queue local update/archive/reactivation finalization without repeating the provider mutation"
10646
+ });
10647
+ const result = await api2.integration.reconcileResourceOperation.mutate({
10648
+ operationId
10649
+ });
10650
+ out(result);
10651
+ };
10566
10652
  var listInboundSources = async (input) => {
10567
10653
  const result = await api2.inbound.listSources.query(input);
10568
10654
  out(result);
10569
10655
  };
10656
+ var countInboundSources = async (input) => {
10657
+ const result = await api2.inbound.countSources.query(input);
10658
+ out(result);
10659
+ };
10570
10660
  var createInboundSource = async (input) => {
10571
10661
  const result = await api2.inbound.createSource.mutate(input);
10572
10662
  out(result);
@@ -10583,6 +10673,9 @@ var listInboundSubmissions = async (input) => {
10583
10673
  const result = await api2.inbound.listSubmissions.query(input);
10584
10674
  out(result);
10585
10675
  };
10676
+ var countInboundSubmissions = async (input) => {
10677
+ out(await api2.inbound.countSubmissions.query(input));
10678
+ };
10586
10679
  var getInboundSubmission = async (submissionId) => {
10587
10680
  const result = await api2.inbound.getSubmission.query({ submissionId });
10588
10681
  out(result);
@@ -10610,39 +10703,32 @@ async function listContacts(companyId) {
10610
10703
  });
10611
10704
  out(contacts);
10612
10705
  }
10613
- async function listItems(opts) {
10614
- const filters = {
10615
- includeQuickBooksDeactivationPending: opts.includeQuickBooksDeactivationPending,
10616
- name: opts.name
10617
- };
10618
- const perPage = opts.perPage ?? 20;
10619
- const [items, { total, counts }] = await Promise.all([
10620
- api2.item.getItems.query({ ...filters, page: opts.page ?? 1, perPage }),
10621
- api2.item.getItemCounts.query(filters)
10622
- ]);
10623
- out({ items, pageCount: Math.ceil(total / perPage), counts });
10624
- }
10706
+ var listItems = async (input) => {
10707
+ out(await api2.item.getItems.query(input));
10708
+ };
10709
+ var countItems = async (input) => {
10710
+ out(await api2.item.getItemCounts.query(input));
10711
+ };
10625
10712
  async function getItem(id) {
10626
10713
  const item = await api2.item.getItem.query({ id });
10627
10714
  out(item);
10628
10715
  }
10629
- async function listItemCategories(opts) {
10630
- const result = await api2.itemCategory.getItemCategories.query({
10631
- page: opts.page ?? 1,
10632
- perPage: opts.perPage ?? 50
10633
- });
10716
+ var getItemQuickBooksSyncStatus = async (itemId) => {
10717
+ const result = await api2.item.getQuickBooksSyncStatus.query({ id: itemId });
10634
10718
  out(result);
10635
- }
10636
- async function getSupplierAnalytics(opts) {
10637
- const result = await api2.dashboard.getSupplierTableData.query({
10638
- page: opts.page ?? 1,
10639
- perPage: opts.perPage ?? 20,
10640
- sort: [],
10641
- name: opts.name,
10642
- timeFrame: opts.timeFrame ?? "ALL"
10643
- });
10644
- out(result);
10645
- }
10719
+ };
10720
+ var listItemCategories = async (input) => {
10721
+ out(await api2.itemCategory.getItemCategories.query(input));
10722
+ };
10723
+ var countItemCategories = async (input) => {
10724
+ out(await api2.itemCategory.getCounts.query(input));
10725
+ };
10726
+ var getSupplierAnalytics = async (input) => {
10727
+ out(await api2.dashboard.getSupplierTableData.query(input));
10728
+ };
10729
+ var countSupplierAnalytics = async (input) => {
10730
+ out(await api2.dashboard.getSupplierTableCounts.query(input));
10731
+ };
10646
10732
  async function createUser(input) {
10647
10733
  const user = await api2.user.create.mutate(input);
10648
10734
  out(user);
@@ -10659,10 +10745,12 @@ async function revokeApiKeyForUser(input) {
10659
10745
  const result = await api2.mcpApiKey.revoke.mutate(input);
10660
10746
  out(result);
10661
10747
  }
10662
- async function listUsers() {
10663
- const users = await api2.user.getAllUsers.query();
10664
- out(users);
10665
- }
10748
+ var listUsers = async (input) => {
10749
+ out(await api2.user.searchUsers.query(input));
10750
+ };
10751
+ var countUsers = async (input) => {
10752
+ out(await api2.user.getCounts.query(input));
10753
+ };
10666
10754
  async function whoAmI() {
10667
10755
  const user = await api2.user.getCurrentUser.query();
10668
10756
  out(user);
@@ -10726,33 +10814,34 @@ async function getFinancialOverview(input) {
10726
10814
  });
10727
10815
  out(result);
10728
10816
  }
10729
- async function getRecentErrors(opts) {
10730
- const result = await api2.errorLog.getRecentErrors.query({
10731
- page: opts.page ?? 1,
10732
- perPage: opts.perPage ?? 10,
10733
- severity: opts.severity,
10734
- status: opts.status
10735
- });
10736
- out(result);
10737
- }
10817
+ var getRecentErrors = async (input) => {
10818
+ out(await api2.errorLog.getRecentErrors.query(input));
10819
+ };
10820
+ var countErrors = async (input) => {
10821
+ out(await api2.errorLog.countErrors.query(input));
10822
+ };
10738
10823
  async function getErrorMetrics() {
10739
10824
  const result = await api2.errorLog.getErrorMetrics.query({});
10740
10825
  out(result);
10741
10826
  }
10742
10827
  async function getApprovedBudgets(opts) {
10743
- const perPage = Math.min(opts.limit ?? 50, 100);
10744
- const { budgets, totalCount } = await api2.budget.getAllBudgets.query({
10828
+ const filters = {
10745
10829
  projectId: opts.projectId,
10746
10830
  statuses: [
10747
10831
  ExtendedBudgetStatus.APPROVED,
10748
10832
  ExtendedBudgetStatus.ESTIMATE_ACCEPTED,
10749
10833
  ExtendedBudgetStatus.ESTIMATE_CLOSED
10750
- ],
10751
- page: 1,
10752
- perPage,
10753
- includeDetails: false
10754
- });
10755
- const items = (budgets ?? []).map((b) => {
10834
+ ]
10835
+ };
10836
+ const [{ items: budgets }, { totalCount }] = await Promise.all([
10837
+ api2.budget.getAllBudgets.query({
10838
+ ...filters,
10839
+ limit: Math.min(opts.limit ?? 50, 100),
10840
+ includeDetails: false
10841
+ }),
10842
+ api2.budget.getCounts.query(filters)
10843
+ ]);
10844
+ const items = budgets.map((b) => {
10756
10845
  const calc = b;
10757
10846
  const revenue = calc.totalSellingAfterDiscount ?? 0;
10758
10847
  const cost = calc.totalCostWithoutGst ?? 0;
@@ -10840,7 +10929,7 @@ function parseArgs(argv) {
10840
10929
  }
10841
10930
  }
10842
10931
  } else if (arg === "-q") {
10843
- flags.q = true;
10932
+ flags.quiet = true;
10844
10933
  } else {
10845
10934
  positional.push(arg);
10846
10935
  }
@@ -11192,6 +11281,7 @@ var financialWriteTargets = new Set([
11192
11281
  ]);
11193
11282
  var deleteTargets = new Set([
11194
11283
  "cleanup-staged-bill-attachments",
11284
+ "delete-webhook-endpoint",
11195
11285
  "cleanup-staged-quotation-attachments",
11196
11286
  "discard-customer-invoice",
11197
11287
  "complete-project",
@@ -11200,7 +11290,14 @@ var deleteTargets = new Set([
11200
11290
  "restore-budget-version",
11201
11291
  "update-project-status"
11202
11292
  ]);
11293
+ var explicitEffects = {
11294
+ "rotate-webhook-secret": ["state-change"],
11295
+ "replay-webhook-delivery": ["state-change", "external-write"]
11296
+ };
11203
11297
  var effectsFor = (legacyTarget) => {
11298
+ const explicit = explicitEffects[legacyTarget];
11299
+ if (explicit)
11300
+ return explicit;
11204
11301
  const effects = new Set;
11205
11302
  if (mutationPrefixes.some((prefix) => legacyTarget.startsWith(prefix))) {
11206
11303
  effects.add("state-change");
@@ -11218,16 +11315,56 @@ var effectsFor = (legacyTarget) => {
11218
11315
  };
11219
11316
  var titleCase = (value) => value.split("-").map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`).join(" ");
11220
11317
  var summaryFor = (legacyTarget) => {
11318
+ if (legacyTarget === "update-budget-status")
11319
+ return "Update budget status. APPROVED requires a current Lead/Admin role; budget management permission alone is insufficient. Ordinary users must request approval. Status changes invalidate old pending requests.";
11320
+ if (legacyTarget === "approve-budget" || legacyTarget === "reject-budget")
11321
+ return "Decide your assigned pending budget approval as a current Lead/Admin. Material edits supersede old requests; inspect the latest content and use a fresh request after edits.";
11322
+ if (legacyTarget === "create-budget-approval")
11323
+ return "Request budget approval for the current content, superseding older pending requests. Existing Lead/Admin autoapproval remains available.";
11324
+ if (legacyTarget === "update-budget-item-supplier")
11325
+ return "Change a budget item's eligible supplier/GST snapshot as a material financial edit. Supersede pending approvals and apply the standard budget-to-DRAFT reset policy; inspect and request approval again. An unchanged supplier is a no-op.";
11326
+ if (legacyTarget === "submit-quotation")
11327
+ return "Submit a quotation for signing and approval. Claim the captured revision and exact allocation selection; concurrent edits, budget-item deletion or restore cascades conflict instead of attaching old signed PDFs. Refresh and resubmit the current content.";
11328
+ if (legacyTarget === "approve-quotation")
11329
+ return "Approve your assigned pending quotation as a current Admin. Claim the captured revision and exact allocation selection after signing; concurrent changes conflict and roll back the decision, signed attachment and email/outbox. Refresh and review the current content before retrying.";
11221
11330
  if (legacyTarget === "approve-supplier")
11222
11331
  return "Approve a pending supplier request and send a requester reply email, or directly re-approve a rejected supplier as Admin/Lead without a pending request or email. QuickBooks is synchronized; archived suppliers remain archived. Argument: <supplierId>.";
11332
+ if (legacyTarget === "get-bill-attachments")
11333
+ return "List bill attachments with cursor pagination. <billId> [--limit 1-50] [--cursor <token>] Newest first. Returns { items, pageInfo }; follow nextCursor.";
11334
+ if (legacyTarget === "get-budget-categories")
11335
+ return "List budget categories with cursor pagination. [--q] [--limit 1-100] [--cursor <token>] Active categories, name A\u2013Z. Returns { items, pageInfo }; follow nextCursor with the same --q.";
11336
+ if (legacyTarget === "get-supplier-analytics")
11337
+ return "List supplier analytics rows with cursor pagination. [--name] [--status CSV] [--gstRegistered true|false] [--createdBy <text>] [--timeFrame ALL|LAST_YEAR|LAST_6_MONTHS|LAST_3_MONTHS|CUSTOM] [--startDate <ISO> --endDate <ISO>] [--sortBy name|gstRegistered|status|phone|email|updatedAt|createdAt] [--sortDir asc|desc] [--limit 1-100] [--cursor <token>] [--direction forward|backward] Admin. Returns { items, pageInfo }; follow nextCursor/previousCursor with the same filters and sort. --page/--perPage are rejected.";
11338
+ if (legacyTarget === "list-api-keys")
11339
+ return "List API keys with cursor pagination. [--userId <userId>] [--limit 1-100] [--cursor <token>] (newest first, { items, pageInfo }; defaults to the caller; admin required for another user)";
11340
+ if (legacyTarget === "list-claims")
11341
+ return "List reimbursable claims with cursor pagination. Same flags as list-bills (--limit 1-100, --cursor, --direction forward|backward); ignores --isClaimable. Returns { items, pageInfo }.";
11342
+ if (legacyTarget === "list-bills")
11343
+ return "List bills with cursor pagination. [--projectId] [--budgetId] [--status CSV] [--search <text>] [--isClaimable true|false] [--createdByIds <csv>] [--sortBy createdAt|amount|status] [--sortDir asc|desc] [--limit 1-100] [--cursor <token>] [--direction forward|backward] Returns { items, pageInfo }; pass nextCursor with --direction forward or previousCursor with --direction backward, keeping the same filters and sort. --page/--pageSize/--perPage are rejected.";
11344
+ if (legacyTarget === "list-customer-invoices")
11345
+ return "List customer invoice batches with cursor pagination. [budgetId] [--projectId <id>] [--companyIds CSV] [--projectIds CSV] [--budgetIds CSV] [--createdByIds CSV] [--status CSV] [--search] [--sortBy createdAt|status|totalInvoiceAmount|approvalExpiresAt] [--sortDir asc|desc] [--limit 1-100] [--cursor <token>] [--direction forward|backward] Returns { items, pageInfo }; follow nextCursor/previousCursor with the same filters and sort. --page/--perPage are rejected. No scope flag lists invoices globally. --projectId uses the project scope. A positional budgetId is a backward-compatible shortcut for --budgetIds.";
11346
+ if (legacyTarget === "list-quotations")
11347
+ return "List quotations with cursor pagination. [--projectId] [--budgetId] [--supplierId] [--status CSV] [--createdBy CSV] [--search] [--limit 1-100] [--cursor <token>] [--direction forward|backward] Newest first; returns { items, pageInfo }. Follow nextCursor/previousCursor with the same filters. --page/--pageSize/--perPage are rejected; --requesterIds remains an alias for --createdBy.";
11348
+ if (legacyTarget === "list-users")
11349
+ return "List users with cursor pagination. [--q] [--limit 1-100] [--cursor <token>] Name A\u2013Z. Returns { items, pageInfo }; follow nextCursor with the same --q. --page/--perPage are rejected.";
11350
+ if (legacyTarget === "list-companies")
11351
+ return "List companies with cursor pagination. Returns { items, pageInfo }; default sort is createdAt descending with limit 20. Pass nextCursor forward or previousCursor backward unchanged. Legacy page/perPage flags are rejected.";
11352
+ if (legacyTarget === "count-companies")
11353
+ return "Count companies independently from list pagination. Returns { totalCount, counts: { active: { true, false } } }; accepts filters only and rejects pagination/sorting flags.";
11354
+ if (legacyTarget === "list-projects")
11355
+ return "List projects with cursor pagination. Returns { items, pageInfo }; default sort is createdAt descending with limit 20. Supports legacy --status plus optional --statuses, WON-date filters (--wonFrom, --wonTo, --wonWithinDays) and --sortBy wonAt; pass nextCursor forward or previousCursor backward unchanged. Legacy page/perPage flags are rejected.";
11356
+ if (legacyTarget === "count-projects")
11357
+ return "Count projects independently from list pagination. Returns { totalCount, counts: { status, active: { true, false } } }; accepts project filters only and rejects pagination/sorting flags.";
11223
11358
  if (legacyTarget === "search-existing-companies")
11224
- return "Search BB and QuickBooks companies without writes. --query <name> [--page <number>].";
11359
+ return "Search BB and QuickBooks companies without writes. --query <name> [--limit 1-50] [--cursor <token>]. Returns { items, totalCount, pageInfo }; follow pageInfo.nextCursor with the same --query. Legacy --page is rejected.";
11225
11360
  if (legacyTarget === "use-existing-company")
11226
11361
  return "Reuse a company in BB and QuickBooks, creating only its missing counterpart. Supply --companyId <bbId> and/or --quickbooksId <qboId>; both explicitly connect records with different names. --website <url> fills a missing QBO import website only. --confirmDistinct true confirms no similar record represents this entity.";
11227
11362
  if (legacyTarget === "create-company")
11228
11363
  return "Create or reuse a company in BB and QuickBooks. --name <name> --website <url>. Similar names require --confirmDistinct true; inactive companies are never reactivated.";
11229
11364
  if (legacyTarget === "create-project")
11230
11365
  return "Create a project linked to an existing Asana Deals lead task. Supply --asanaTaskId in non-interactive use; interactive use searches for and selects a lead task (optionally --asanaSearch). The Slack channel is resolved from the selected Asana task unless all three Slack channel flags are supplied. For a new direct client, create the lead task using the website form first.";
11366
+ if (legacyTarget === "create-supplier")
11367
+ return "Create a supplier using --payload JSON; pending-approval suppliers also request approval and send emails. Reusing an archived name requires Admin; normal users and Leads must ask an Admin to reactivate it before editing. Recreation preserves the existing approval status and creator.";
11231
11368
  if (legacyTarget === "preview-qbo-customer-invoice") {
11232
11369
  return "Preview an available QuickBooks invoice selected for a BB budget using read-only QBO access. Arguments: <budgetId> <qboInvoiceId>.";
11233
11370
  }
@@ -11243,8 +11380,22 @@ var summaryFor = (legacyTarget) => {
11243
11380
  if (legacyTarget === "whoami")
11244
11381
  return "Show the active API-key identity.";
11245
11382
  if (legacyTarget === "list-budgets") {
11246
- return "List budgets. Omit --active for active budgets only; use --active false or --active all for Admin-only historical reads.";
11247
- }
11383
+ return "List one cursor page of budgets ({ items, pageInfo }; --limit/--cursor/--direction). Omit --active for active budgets only; use --active false or --active all for Admin-only historical reads.";
11384
+ }
11385
+ if (legacyTarget === "list-items")
11386
+ return "List one cursor page of catalogue items ({ items, pageInfo }; --limit/--cursor/--direction, optional --sortBy/--sortDir). --page/--perPage are rejected.";
11387
+ if (legacyTarget === "list-item-categories")
11388
+ return "List one cursor page of active item categories ({ items, pageInfo }; --limit/--cursor/--direction, optional --name/--sortBy/--sortDir). --page/--perPage are rejected.";
11389
+ if (legacyTarget === "count-item-categories")
11390
+ return "Count active item categories matching --name: { totalCount }.";
11391
+ if (legacyTarget === "list-integration-operations")
11392
+ return "List one cursor page of integration operations, newest first ({ items, pageInfo }; --limit/--cursor/--direction). --page/--perPage are rejected. Admin only.";
11393
+ if (legacyTarget === "count-integration-operations")
11394
+ return "Count integration operations matching filters: { totalCount, filterOptions }. Admin only.";
11395
+ if (legacyTarget === "count-items")
11396
+ return "Count catalogue items matching filters: total plus category and supplier facets (each ignores its own filter).";
11397
+ if (legacyTarget === "count-budgets")
11398
+ return "Count budgets matching filters: totalCount plus status and active facets. Omit --active for active budgets only.";
11248
11399
  if (legacyTarget === "get-budget") {
11249
11400
  return "Get a budget by ID. Omit --includeInactive for active budgets only; --includeInactive is an Admin-only historical read.";
11250
11401
  }
@@ -11270,6 +11421,8 @@ var summaryFor = (legacyTarget) => {
11270
11421
  if (legacyTarget === "create-quotation") {
11271
11422
  return 'Create a quotation with an approved supplier using --payload JSON. Include projectId, budgetId, supplierId, budgetItemIds, amountWithoutGst, gstAmount, and one uploaded attachment in attachments. Multiple budget items require lineAmounts: [{"budgetItemId":"<first-id>","amount":750},{"budgetItemId":"<second-id>","amount":450}], covering every selected budget-item ID exactly once and summing to amountWithoutGst. Amounts exclude GST; use gstAmount: 0 for no GST. Example: amountWithoutGst: 1200 with line amounts 750 and 450.';
11272
11423
  }
11424
+ if (legacyTarget === "cleanup-staged-quotation-attachments")
11425
+ return "Queue independent cleanup operations for unattached staged quotation uploads; returns operation IDs and queued/skipped counts, not deletion confirmation. Arguments: <projectId> --keys <csv>.";
11273
11426
  if (legacyTarget === "update-quotation") {
11274
11427
  return 'Update a DRAFT or REJECTED quotation using --payload JSON with id and the complete creation payload. Multiple budget items require lineAmounts: [{"budgetItemId":"<first-id>","amount":750},{"budgetItemId":"<second-id>","amount":450}], covering every budgetItemIds entry exactly once and summing to amountWithoutGst. Amounts exclude GST; use gstAmount: 0 for no GST.';
11275
11428
  }
@@ -11279,6 +11432,30 @@ var summaryFor = (legacyTarget) => {
11279
11432
  if (legacyTarget === "send-customer-invoice-to-contact-person") {
11280
11433
  return "Send a customer invoice and update the project billing email.";
11281
11434
  }
11435
+ if (legacyTarget === "update-company")
11436
+ return "Save company fields in BB and queue independent QuickBooks name/website synchronization; returns operation ID, not provider completion.";
11437
+ if (legacyTarget === "get-company-quickbooks-sync-status")
11438
+ return "Read saved company synchronization status without provider calls or polling; use companyId.";
11439
+ if (legacyTarget === "get-item-quickbooks-sync-status")
11440
+ return "Read saved item QuickBooks synchronization status without provider calls or polling; use itemId.";
11441
+ if (legacyTarget === "create-webhook-endpoint")
11442
+ return "Admin-only: subscribe an HTTPS endpoint to BB events. --name --url --events <type|prefix.*|*,...>. Prints the whsec_ signing secret once; deliveries use Standard Webhooks headers.";
11443
+ if (legacyTarget === "rotate-webhook-secret")
11444
+ return "Admin-only: issue a new signing secret; older secrets keep signing for --previousSecretTtlHours (default 24). Prints the new secret once. Argument: <endpointId>.";
11445
+ if (legacyTarget === "send-webhook-test-event")
11446
+ return "Admin-only: send a signed webhook.test event to one endpoint. Argument: <endpointId>.";
11447
+ if (legacyTarget === "replay-webhook-delivery")
11448
+ return "Admin-only: resend a finished webhook delivery with the same eventId on a fresh attempt chain. Argument: <deliveryId>.";
11449
+ if (legacyTarget === "get-webhook-delivery")
11450
+ return "Admin-only: one webhook delivery with payload and attempts (status codes, errors, durations). Argument: <deliveryId>.";
11451
+ if (legacyTarget === "list-webhook-event-types")
11452
+ return "List the outbound webhook event catalogue with group and live/planned status.";
11453
+ if (legacyTarget === "reconcile-resource-operation")
11454
+ return "Admin-only check of a saved QuickBooks invoice/company/item job outcome; reads the provider only when uncertain. Applied changes are finished locally without repeating the provider mutation; provably unapplied ones are queued again. Argument: <operationId>.";
11455
+ if (legacyTarget === "get-integration-operation")
11456
+ return "Admin-only read of one integration job: who queued it, the records it is for with app links, saved QuickBooks progress, admin actions and Retry state. No provider calls. Argument: <operationId>.";
11457
+ if (legacyTarget === "retry-integration-operation")
11458
+ return "Admin-only retry of a failed integration job. QuickBooks jobs with a saved attempt check QuickBooks first: applied changes finish without resending, provably unapplied ones are sent again, unprovable ones stay stopped with the reason. Argument: <operationId>.";
11282
11459
  if (legacyTarget.startsWith("list-")) {
11283
11460
  return `List ${legacyTarget.slice("list-".length).replaceAll("-", " ")}.`;
11284
11461
  }
@@ -11329,6 +11506,24 @@ var registry2 = [
11329
11506
  authCommand("logout", "Revoke the interactive CLI session and remove its local credential."),
11330
11507
  legacyCommand("whoami", ["auth", "whoami"]),
11331
11508
  legacyCommand("list-budgets", ["budget", "list"], [], [
11509
+ {
11510
+ name: "--active <true|false|all>",
11511
+ description: "Omit for active budgets only; false or all requires Admin access."
11512
+ },
11513
+ {
11514
+ name: "--limit <1-100>",
11515
+ description: "Rows per page. Output is { items, pageInfo }."
11516
+ },
11517
+ {
11518
+ name: "--cursor <token>",
11519
+ description: "pageInfo.nextCursor (or previousCursor with --direction backward) from the last page, unchanged; keep the same filters and sort."
11520
+ },
11521
+ {
11522
+ name: "--direction <forward|backward>",
11523
+ description: "forward (default) or backward; backward needs --cursor."
11524
+ }
11525
+ ]),
11526
+ legacyCommand("count-budgets", ["budget", "count"], [], [
11332
11527
  {
11333
11528
  name: "--active <true|false|all>",
11334
11529
  description: "Omit for active budgets only; false or all requires Admin access."
@@ -11354,7 +11549,20 @@ var registry2 = [
11354
11549
  description: "Include inactive historical budgets; Admin only."
11355
11550
  }
11356
11551
  ]),
11357
- legacyCommand("get-budget-categories", ["budget", "category", "list"]),
11552
+ legacyCommand("get-budget-categories", ["budget", "category", "list"], [], [
11553
+ {
11554
+ name: "--q <text>",
11555
+ description: "Search active category names (name A\u2013Z). A bare --q is rejected; use -q/--quiet to hide logs."
11556
+ },
11557
+ {
11558
+ name: "--limit <1-100>",
11559
+ description: "Rows per page. Output is { items, pageInfo }."
11560
+ },
11561
+ {
11562
+ name: "--cursor <token>",
11563
+ description: "pageInfo.nextCursor from the last page, unchanged; pages forward only."
11564
+ }
11565
+ ]),
11358
11566
  legacyCommand("get-budget-versions", ["budget", "version", "list"], [], [
11359
11567
  {
11360
11568
  name: "--limit <1-50>",
@@ -11414,8 +11622,36 @@ var registry2 = [
11414
11622
  "placeholder-bill",
11415
11623
  "create"
11416
11624
  ]),
11417
- legacyCommand("list-bills", ["bill", "list"]),
11418
- legacyCommand("list-claims", ["claim", "list"]),
11625
+ legacyCommand("list-bills", ["bill", "list"], [], [
11626
+ {
11627
+ name: "--limit <1-100>",
11628
+ description: "Rows per page. Output is { items, pageInfo }."
11629
+ },
11630
+ {
11631
+ name: "--cursor <token>",
11632
+ description: "pageInfo.nextCursor (or previousCursor with --direction backward) from the last page, unchanged; keep the same filters and sort."
11633
+ },
11634
+ {
11635
+ name: "--direction <forward|backward>",
11636
+ description: "forward (default) or backward; backward needs --cursor."
11637
+ }
11638
+ ]),
11639
+ legacyCommand("list-claims", ["claim", "list"], [], [
11640
+ {
11641
+ name: "--limit <1-100>",
11642
+ description: "Rows per page. Output is { items, pageInfo }."
11643
+ },
11644
+ {
11645
+ name: "--cursor <token>",
11646
+ description: "pageInfo.nextCursor (or previousCursor with --direction backward) from the last page, unchanged; keep the same filters and sort."
11647
+ },
11648
+ {
11649
+ name: "--direction <forward|backward>",
11650
+ description: "forward (default) or backward; backward needs --cursor."
11651
+ }
11652
+ ]),
11653
+ legacyCommand("count-bills", ["bill", "count"]),
11654
+ legacyCommand("count-claims", ["claim", "count"]),
11419
11655
  legacyCommand("create-bill-approval", ["bill", "approval", "create"]),
11420
11656
  legacyCommand("create-bill", ["bill", "create"], [], [
11421
11657
  {
@@ -11471,7 +11707,16 @@ var registry2 = [
11471
11707
  "invoice-number",
11472
11708
  "patch"
11473
11709
  ]),
11474
- legacyCommand("get-bill-attachments", ["bill", "attachment", "list"]),
11710
+ legacyCommand("get-bill-attachments", ["bill", "attachment", "list"], [], [
11711
+ {
11712
+ name: "--limit <1-50>",
11713
+ description: "Rows per page. Output is { items, pageInfo }."
11714
+ },
11715
+ {
11716
+ name: "--cursor <token>",
11717
+ description: "pageInfo.nextCursor from the last page, unchanged; pages forward only."
11718
+ }
11719
+ ]),
11475
11720
  legacyCommand("stage-bill-attachment", ["bill", "attachment", "stage"]),
11476
11721
  legacyCommand("cleanup-staged-bill-attachments", [
11477
11722
  "bill",
@@ -11487,7 +11732,21 @@ var registry2 = [
11487
11732
  ]),
11488
11733
  legacyCommand("attach-bill-documents", ["bill", "document", "attach"]),
11489
11734
  legacyCommand("get-bill-details", ["bill", "get"]),
11490
- legacyCommand("list-quotations", ["quotation", "list"]),
11735
+ legacyCommand("list-quotations", ["quotation", "list"], [], [
11736
+ {
11737
+ name: "--limit <1-100>",
11738
+ description: "Rows per page. Output is { items, pageInfo }."
11739
+ },
11740
+ {
11741
+ name: "--cursor <token>",
11742
+ description: "pageInfo.nextCursor (or previousCursor with --direction backward) from the last page, unchanged; keep the same filters and sort."
11743
+ },
11744
+ {
11745
+ name: "--direction <forward|backward>",
11746
+ description: "forward (default) or backward; backward needs --cursor."
11747
+ }
11748
+ ]),
11749
+ legacyCommand("count-quotations", ["quotation", "count"]),
11491
11750
  legacyCommand("get-quotation", ["quotation", "get"]),
11492
11751
  legacyCommand("upload-quotation-attachment", [
11493
11752
  "quotation",
@@ -11539,7 +11798,21 @@ var registry2 = [
11539
11798
  "existing",
11540
11799
  "list"
11541
11800
  ]),
11542
- legacyCommand("list-customer-invoices", ["customer-invoice", "list"]),
11801
+ legacyCommand("list-customer-invoices", ["customer-invoice", "list"], [], [
11802
+ {
11803
+ name: "--limit <1-100>",
11804
+ description: "Rows per page. Output is { items, pageInfo }."
11805
+ },
11806
+ {
11807
+ name: "--cursor <token>",
11808
+ description: "pageInfo.nextCursor (or previousCursor with --direction backward) from the last page, unchanged; keep the same filters and sort."
11809
+ },
11810
+ {
11811
+ name: "--direction <forward|backward>",
11812
+ description: "forward (default) or backward; backward needs --cursor."
11813
+ }
11814
+ ]),
11815
+ legacyCommand("count-customer-invoices", ["customer-invoice", "count"]),
11543
11816
  legacyCommand("get-customer-invoice", ["customer-invoice", "get"]),
11544
11817
  legacyCommand("get-customer-invoice-email-context", [
11545
11818
  "customer-invoice",
@@ -11578,7 +11851,62 @@ var registry2 = [
11578
11851
  legacyCommand("approve-supplier", ["supplier", "approve"]),
11579
11852
  legacyCommand("reject-supplier", ["supplier", "reject"]),
11580
11853
  legacyCommand("get-supplier-details", ["supplier", "detail", "get"]),
11581
- legacyCommand("list-companies", ["company", "list"]),
11854
+ legacyCommand("list-companies", ["company", "list"], [], [
11855
+ {
11856
+ name: "--name <text>",
11857
+ description: "Filter by company name substring."
11858
+ },
11859
+ {
11860
+ name: "--createdBy <userId>",
11861
+ description: "Filter by one creator user ID."
11862
+ },
11863
+ {
11864
+ name: "--createdByIds <csv>",
11865
+ description: "Filter by a comma-separated creator user ID list."
11866
+ },
11867
+ {
11868
+ name: "--active <true|false>",
11869
+ description: "Filter by active status; omit to include both states."
11870
+ },
11871
+ {
11872
+ name: "--sortBy <name|createdAt>",
11873
+ description: "Sort field (default createdAt)."
11874
+ },
11875
+ {
11876
+ name: "--sortDir <asc|desc>",
11877
+ description: "Sort direction (default desc)."
11878
+ },
11879
+ {
11880
+ name: "--limit <1-100>",
11881
+ description: "Bounded page size (default 20)."
11882
+ },
11883
+ {
11884
+ name: "--cursor <token>",
11885
+ description: "Opaque pageInfo cursor; pass nextCursor forward or previousCursor backward unchanged."
11886
+ },
11887
+ {
11888
+ name: "--direction <forward|backward>",
11889
+ description: "Use forward by default; backward requires --cursor."
11890
+ }
11891
+ ]),
11892
+ legacyCommand("count-companies", ["company", "count"], [], [
11893
+ {
11894
+ name: "--name <text>",
11895
+ description: "Filter by company name substring."
11896
+ },
11897
+ {
11898
+ name: "--createdBy <userId>",
11899
+ description: "Filter by one creator user ID."
11900
+ },
11901
+ {
11902
+ name: "--createdByIds <csv>",
11903
+ description: "Filter by a comma-separated creator user ID list."
11904
+ },
11905
+ {
11906
+ name: "--active <true|false>",
11907
+ description: "Filter by active status; facets omit this self-filter."
11908
+ }
11909
+ ]),
11582
11910
  legacyCommand("get-company", ["company", "get"]),
11583
11911
  legacyCommand("search-existing-companies", ["company", "search"], [], [
11584
11912
  {
@@ -11586,8 +11914,12 @@ var registry2 = [
11586
11914
  description: "Company name to search in BB and QuickBooks."
11587
11915
  },
11588
11916
  {
11589
- name: "--page",
11590
- description: "Results page (eight companies per page)."
11917
+ name: "--limit",
11918
+ description: "Matches per page, 1\u201350 (default 8)."
11919
+ },
11920
+ {
11921
+ name: "--cursor",
11922
+ description: "pageInfo.nextCursor from the previous page, unchanged."
11591
11923
  }
11592
11924
  ]),
11593
11925
  legacyCommand("use-existing-company", ["company", "use"], [], [
@@ -11615,8 +11947,94 @@ var registry2 = [
11615
11947
  }
11616
11948
  ]),
11617
11949
  legacyCommand("update-company", ["company", "update"]),
11950
+ legacyCommand("get-company-quickbooks-sync-status", [
11951
+ "company",
11952
+ "sync",
11953
+ "status"
11954
+ ]),
11618
11955
  legacyCommand("delete-company", ["company", "delete"]),
11619
- legacyCommand("list-projects", ["project", "list"]),
11956
+ legacyCommand("list-projects", ["project", "list"], [], [
11957
+ { name: "--companyId <id>", description: "Filter by company ID." },
11958
+ {
11959
+ name: "--name <text>",
11960
+ description: "Filter by project name substring."
11961
+ },
11962
+ {
11963
+ name: "--status <status|ALL>",
11964
+ description: "Legacy single-status filter; use --statuses for multiple statuses."
11965
+ },
11966
+ {
11967
+ name: "--statuses <csv>",
11968
+ description: "Filter by a comma-separated project status list."
11969
+ },
11970
+ {
11971
+ name: "--active <true|false>",
11972
+ description: "Filter by active status; omit to include both states."
11973
+ },
11974
+ {
11975
+ name: "--wonFrom <iso>",
11976
+ description: "Only projects marked WON on or after this ISO date or datetime (UTC)."
11977
+ },
11978
+ {
11979
+ name: "--wonTo <iso>",
11980
+ description: "Only projects marked WON on or before this ISO date (inclusive) or datetime (UTC)."
11981
+ },
11982
+ {
11983
+ name: "--wonWithinDays <1-3650>",
11984
+ description: "Only projects marked WON in the last N days."
11985
+ },
11986
+ {
11987
+ name: "--limit <1-100>",
11988
+ description: "Bounded page size (default 20)."
11989
+ },
11990
+ {
11991
+ name: "--cursor <token>",
11992
+ description: "Opaque pageInfo cursor; pass nextCursor forward or previousCursor backward unchanged."
11993
+ },
11994
+ {
11995
+ name: "--direction <forward|backward>",
11996
+ description: "Use forward by default; backward requires --cursor."
11997
+ },
11998
+ {
11999
+ name: "--sortBy <name|createdAt|status|companyName|wonAt>",
12000
+ description: "Sort field (default createdAt); wonAt puts unwon projects last."
12001
+ },
12002
+ {
12003
+ name: "--sortDir <asc|desc>",
12004
+ description: "Sort direction (default desc)."
12005
+ }
12006
+ ]),
12007
+ legacyCommand("count-projects", ["project", "count"], [], [
12008
+ { name: "--companyId <id>", description: "Filter by company ID." },
12009
+ {
12010
+ name: "--name <text>",
12011
+ description: "Filter by project name substring."
12012
+ },
12013
+ {
12014
+ name: "--status <status|ALL>",
12015
+ description: "Legacy single-status filter."
12016
+ },
12017
+ {
12018
+ name: "--statuses <csv>",
12019
+ description: "Filter by a comma-separated project status list."
12020
+ },
12021
+ {
12022
+ name: "--active <true|false>",
12023
+ description: "Filter by active status; facets exclude this self-filter."
12024
+ },
12025
+ {
12026
+ name: "--wonFrom <iso>",
12027
+ description: "Only projects marked WON on or after this ISO date or datetime (UTC)."
12028
+ },
12029
+ {
12030
+ name: "--wonTo <iso>",
12031
+ description: "Only projects marked WON on or before this ISO date (inclusive) or datetime (UTC)."
12032
+ },
12033
+ {
12034
+ name: "--wonWithinDays <1-3650>",
12035
+ description: "Only projects marked WON in the last N days."
12036
+ }
12037
+ ]),
11620
12038
  legacyCommand("list-project-hubs", ["project", "hub", "list"], [], [
11621
12039
  {
11622
12040
  name: "--q",
@@ -11626,14 +12044,31 @@ var registry2 = [
11626
12044
  name: "--status",
11627
12045
  description: "Filter by comma-separated project statuses."
11628
12046
  },
11629
- { name: "--page", description: "Select a results page." },
11630
- { name: "--perPage", description: "Set the results page size." },
12047
+ { name: "--limit", description: "Page size, 1-100 (default 20)." },
12048
+ {
12049
+ name: "--cursor",
12050
+ description: "Opaque nextCursor/previousCursor from the last page."
12051
+ },
12052
+ {
12053
+ name: "--direction",
12054
+ description: "forward (default) or backward; backward needs --cursor."
12055
+ },
11631
12056
  {
11632
12057
  name: "--sortBy",
11633
12058
  description: "Sort by startDate, name, companyName, or status."
11634
12059
  },
11635
12060
  { name: "--sortDir", description: "Sort in asc or desc order." }
11636
12061
  ]),
12062
+ legacyCommand("count-project-hubs", ["project", "hub", "count"], [], [
12063
+ {
12064
+ name: "--q",
12065
+ description: "Search project and company names."
12066
+ },
12067
+ {
12068
+ name: "--status",
12069
+ description: "Filter by comma-separated project statuses."
12070
+ }
12071
+ ]),
11637
12072
  legacyCommand("get-project", ["project", "get"]),
11638
12073
  legacyCommand("get-project-hub-status", ["project", "hub", "status"]),
11639
12074
  legacyCommand("setup-project-hub", ["project", "hub", "setup"]),
@@ -11657,7 +12092,67 @@ var registry2 = [
11657
12092
  legacyCommand("list-contacts", ["contact", "list"]),
11658
12093
  legacyCommand("create-contact-person", ["contact", "create"]),
11659
12094
  legacyCommand("update-contact-person", ["contact", "update"]),
11660
- legacyCommand("list-suppliers", ["supplier", "list"]),
12095
+ legacyCommand("list-suppliers", ["supplier", "list"], [], [
12096
+ { name: "--name <text>", description: "Filter supplier name or email." },
12097
+ { name: "--createdBy <csv>", description: "Filter by creator IDs." },
12098
+ {
12099
+ name: "--gstRegistered <true|false>",
12100
+ description: "Filter GST registration."
12101
+ },
12102
+ {
12103
+ name: "--status <csv>",
12104
+ description: "Filter approval statuses; ALL means no status filter."
12105
+ },
12106
+ {
12107
+ name: "--supplierTags <csv>",
12108
+ description: "Filter by supplier tag IDs."
12109
+ },
12110
+ {
12111
+ name: "--active <true|false>",
12112
+ description: "Active by default; false selects archived."
12113
+ },
12114
+ {
12115
+ name: "--limit <1-100>",
12116
+ description: "Bounded page size (default 10). Retired page/perPage flags are rejected."
12117
+ },
12118
+ {
12119
+ name: "--cursor <token>",
12120
+ description: "Opaque nextCursor or previousCursor from items/pageInfo."
12121
+ },
12122
+ {
12123
+ name: "--direction <forward|backward>",
12124
+ description: "Forward by default; backward requires a cursor."
12125
+ },
12126
+ {
12127
+ name: "--sortBy <field>",
12128
+ description: "name, email, phone, gstRegistered, status, active, createdAt or updatedAt."
12129
+ },
12130
+ {
12131
+ name: "--sortDir <asc|desc>",
12132
+ description: "Descending createdAt by default."
12133
+ },
12134
+ {
12135
+ name: "--sort <json>",
12136
+ description: "Ordered {id,desc} array for multi-column sorting; cannot combine with sortBy/sortDir."
12137
+ }
12138
+ ]),
12139
+ legacyCommand("count-suppliers", ["supplier", "count"], [], [
12140
+ { name: "--name <text>", description: "Filter supplier name or email." },
12141
+ { name: "--createdBy <csv>", description: "Filter by creator IDs." },
12142
+ {
12143
+ name: "--gstRegistered <true|false>",
12144
+ description: "Filter GST registration."
12145
+ },
12146
+ { name: "--status <csv>", description: "Filter approval statuses." },
12147
+ {
12148
+ name: "--supplierTags <csv>",
12149
+ description: "Filter by supplier tag IDs."
12150
+ },
12151
+ {
12152
+ name: "--active <true|false>",
12153
+ description: "Active by default; false selects archived. No pagination/sort flags."
12154
+ }
12155
+ ]),
11661
12156
  legacyCommand("create-supplier", ["supplier", "create"]),
11662
12157
  legacyCommand("update-supplier", ["supplier", "update"]),
11663
12158
  legacyCommand("delete-suppliers", ["supplier", "delete"]),
@@ -11674,21 +12169,87 @@ var registry2 = [
11674
12169
  ]),
11675
12170
  legacyCommand("create-supplier-role", ["supplier", "role", "create"]),
11676
12171
  legacyCommand("create-supplier-tag", ["supplier", "tag", "create"]),
11677
- legacyCommand("get-supplier-analytics", ["supplier", "analytics", "get"]),
11678
- legacyCommand("list-items", ["item", "list"]),
12172
+ legacyCommand("get-supplier-analytics", ["supplier", "analytics", "get"], [], [
12173
+ {
12174
+ name: "--limit <1-100>",
12175
+ description: "Rows per page. Output is { items, pageInfo }."
12176
+ },
12177
+ {
12178
+ name: "--cursor <token>",
12179
+ description: "pageInfo.nextCursor (or previousCursor with --direction backward) from the last page, unchanged; keep the same filters and sort."
12180
+ },
12181
+ {
12182
+ name: "--direction <forward|backward>",
12183
+ description: "forward (default) or backward; backward needs --cursor."
12184
+ }
12185
+ ]),
12186
+ legacyCommand("count-supplier-analytics", ["supplier", "analytics", "count"]),
12187
+ legacyCommand("list-items", ["item", "list"], [], [
12188
+ {
12189
+ name: "--limit <1-100>",
12190
+ description: "Rows per page. Output is { items, pageInfo }."
12191
+ },
12192
+ {
12193
+ name: "--cursor <token>",
12194
+ description: "pageInfo.nextCursor (or previousCursor with --direction backward) from the last page, unchanged; keep the same filters and sort."
12195
+ },
12196
+ {
12197
+ name: "--direction <forward|backward>",
12198
+ description: "forward (default) or backward; backward needs --cursor."
12199
+ }
12200
+ ]),
12201
+ legacyCommand("count-items", ["item", "count"]),
11679
12202
  legacyCommand("get-item", ["item", "get"]),
12203
+ legacyCommand("get-item-quickbooks-sync-status", ["item", "sync", "status"]),
11680
12204
  legacyCommand("create-item", ["item", "create"]),
11681
12205
  legacyCommand("update-item", ["item", "update"]),
11682
12206
  legacyCommand("archive-items", ["item", "archive"]),
11683
12207
  legacyCommand("delete-item", ["item", "delete"]),
11684
- legacyCommand("list-item-categories", ["item", "category", "list"]),
12208
+ legacyCommand("list-item-categories", ["item", "category", "list"], [], [
12209
+ {
12210
+ name: "--limit <1-100>",
12211
+ description: "Rows per page. Output is { items, pageInfo }."
12212
+ },
12213
+ {
12214
+ name: "--cursor <token>",
12215
+ description: "pageInfo.nextCursor (or previousCursor with --direction backward) from the last page, unchanged; keep the same filters and sort."
12216
+ },
12217
+ {
12218
+ name: "--direction <forward|backward>",
12219
+ description: "forward (default) or backward; backward needs --cursor."
12220
+ }
12221
+ ]),
12222
+ legacyCommand("count-item-categories", ["item", "category", "count"]),
11685
12223
  legacyCommand("create-item-category", ["item", "category", "create"]),
11686
12224
  legacyCommand("update-item-category", ["item", "category", "update"]),
11687
12225
  legacyCommand("delete-item-categories", ["item", "category", "delete"]),
11688
- legacyCommand("list-users", ["user", "list"]),
12226
+ legacyCommand("list-users", ["user", "list"], [], [
12227
+ {
12228
+ name: "--q <text>",
12229
+ description: "Search user names and emails (name A\u2013Z). A bare --q is rejected; use -q/--quiet to hide logs."
12230
+ },
12231
+ {
12232
+ name: "--limit <1-100>",
12233
+ description: "Rows per page. Output is { items, pageInfo }."
12234
+ },
12235
+ {
12236
+ name: "--cursor <token>",
12237
+ description: "pageInfo.nextCursor from the last page, unchanged; pages forward only."
12238
+ }
12239
+ ]),
12240
+ legacyCommand("count-users", ["user", "count"]),
11689
12241
  legacyCommand("create-user", ["user", "create"]),
11690
12242
  legacyCommand("create-api-key", ["user", "api-key", "create"]),
11691
- legacyCommand("list-api-keys", ["user", "api-key", "list"]),
12243
+ legacyCommand("list-api-keys", ["user", "api-key", "list"], [], [
12244
+ {
12245
+ name: "--limit <1-100>",
12246
+ description: "Rows per page. Output is { items, pageInfo }."
12247
+ },
12248
+ {
12249
+ name: "--cursor <token>",
12250
+ description: "pageInfo.nextCursor from the last page, unchanged; pages forward only."
12251
+ }
12252
+ ]),
11692
12253
  legacyCommand("revoke-api-key", ["user", "api-key", "revoke"]),
11693
12254
  legacyCommand("get-user-performance", ["user", "performance", "get"]),
11694
12255
  legacyCommand("get-dashboard", ["dashboard", "get"]),
@@ -11704,23 +12265,143 @@ var registry2 = [
11704
12265
  "financial-overview",
11705
12266
  "get"
11706
12267
  ]),
11707
- legacyCommand("get-recent-errors", ["error", "recent", "list"]),
12268
+ legacyCommand("get-recent-errors", ["error", "recent", "list"], [], [
12269
+ {
12270
+ name: "--cursor <token>",
12271
+ description: "Pass a returned pageInfo boundary unchanged, with the same filters."
12272
+ },
12273
+ {
12274
+ name: "--direction <direction>",
12275
+ description: "forward (default) uses nextCursor; backward uses previousCursor."
12276
+ },
12277
+ {
12278
+ name: "--limit <number>",
12279
+ description: "1\u2013100 rows (default 10). Returns items and pageInfo, without a total."
12280
+ },
12281
+ { name: "--severity <severity>", description: "Error severity or ALL." },
12282
+ { name: "--status <status>", description: "Error status or ALL." },
12283
+ {
12284
+ name: "--timeFrame <period>",
12285
+ description: "all_time (default), last_30_days, last_3_months, last_6_months or last_1_year."
12286
+ }
12287
+ ]),
12288
+ legacyCommand("count-errors", ["error", "recent", "count"], [], [
12289
+ { name: "--severity <severity>", description: "Error severity or ALL." },
12290
+ { name: "--status <status>", description: "Error status or ALL." },
12291
+ {
12292
+ name: "--timeFrame <period>",
12293
+ description: "Same filters as error recent list; returns independent totalCount."
12294
+ }
12295
+ ]),
11708
12296
  legacyCommand("get-error-metrics", ["error", "metrics", "get"]),
11709
- legacyCommand("list-integration-operations", [
12297
+ legacyCommand("list-integration-operations", ["integration", "operation", "list"], [], [
12298
+ {
12299
+ name: "--limit <1-100>",
12300
+ description: "Rows per page. Output is { items, pageInfo }."
12301
+ },
12302
+ {
12303
+ name: "--cursor <token>",
12304
+ description: "pageInfo.nextCursor (or previousCursor with --direction backward) from the last page, unchanged; keep the same filters and sort."
12305
+ },
12306
+ {
12307
+ name: "--direction <forward|backward>",
12308
+ description: "forward (default) or backward; backward needs --cursor."
12309
+ }
12310
+ ]),
12311
+ legacyCommand("count-integration-operations", [
11710
12312
  "integration",
11711
12313
  "operation",
11712
- "list"
12314
+ "count"
12315
+ ]),
12316
+ legacyCommand("get-integration-operation", [
12317
+ "integration",
12318
+ "operation",
12319
+ "get"
11713
12320
  ]),
11714
12321
  legacyCommand("retry-integration-operation", [
11715
12322
  "integration",
11716
12323
  "operation",
11717
12324
  "retry"
11718
12325
  ]),
11719
- legacyCommand("list-inbound-sources", ["inbound", "source", "list"]),
12326
+ legacyCommand("reconcile-resource-operation", [
12327
+ "integration",
12328
+ "operation",
12329
+ "reconcile-resource"
12330
+ ]),
12331
+ legacyCommand("list-webhook-event-types", ["webhook", "event-type", "list"]),
12332
+ legacyCommand("list-webhook-endpoints", ["webhook", "endpoint", "list"]),
12333
+ legacyCommand("get-webhook-endpoint", ["webhook", "endpoint", "get"]),
12334
+ legacyCommand("create-webhook-endpoint", ["webhook", "endpoint", "create"]),
12335
+ legacyCommand("update-webhook-endpoint", ["webhook", "endpoint", "update"]),
12336
+ legacyCommand("delete-webhook-endpoint", ["webhook", "endpoint", "delete"]),
12337
+ legacyCommand("rotate-webhook-secret", ["webhook", "secret", "rotate"]),
12338
+ legacyCommand("send-webhook-test-event", ["webhook", "endpoint", "test"]),
12339
+ legacyCommand("list-webhook-deliveries", ["webhook", "delivery", "list"]),
12340
+ legacyCommand("get-webhook-delivery", ["webhook", "delivery", "get"]),
12341
+ legacyCommand("replay-webhook-delivery", ["webhook", "delivery", "replay"]),
12342
+ legacyCommand("list-inbound-sources", ["inbound", "source", "list"], [], [
12343
+ {
12344
+ name: "--limit <n>",
12345
+ description: "Rows per cursor page (1..100; default 25)."
12346
+ },
12347
+ {
12348
+ name: "--cursor <token>",
12349
+ description: "Opaque source continuation token returned by pageInfo."
12350
+ },
12351
+ {
12352
+ name: "--direction <value>",
12353
+ description: "forward or backward (default forward; backward requires cursor)."
12354
+ },
12355
+ {
12356
+ name: "--search <text>",
12357
+ description: "Search source name or Typeform form ID."
12358
+ },
12359
+ {
12360
+ name: "--processingEnabled <boolean>",
12361
+ description: "Filter processing enabled state."
12362
+ },
12363
+ {
12364
+ name: "--processingMode <mode>",
12365
+ description: "ACTIVE, COLLECT_ONLY or SHADOW."
12366
+ }
12367
+ ]),
12368
+ legacyCommand("count-inbound-sources", ["inbound", "source", "count"], [], [
12369
+ {
12370
+ name: "--search <text>",
12371
+ description: "Search source name or Typeform form ID."
12372
+ },
12373
+ {
12374
+ name: "--processingEnabled <boolean>",
12375
+ description: "Filter processing enabled state."
12376
+ },
12377
+ {
12378
+ name: "--processingMode <mode>",
12379
+ description: "ACTIVE, COLLECT_ONLY or SHADOW."
12380
+ }
12381
+ ]),
11720
12382
  legacyCommand("create-inbound-source", ["inbound", "source", "create"]),
11721
12383
  legacyCommand("get-inbound-source", ["inbound", "source", "get"]),
11722
12384
  legacyCommand("update-inbound-source", ["inbound", "source", "update"]),
11723
- legacyCommand("list-inbound-submissions", ["inbound", "submission", "list"]),
12385
+ legacyCommand("list-inbound-submissions", ["inbound", "submission", "list"], [], [
12386
+ {
12387
+ name: "--limit <number>",
12388
+ description: "Page size from 1 to 100 (default 25). Returns submissions, hasMore and nextCursor; no total."
12389
+ },
12390
+ {
12391
+ name: "--cursor <token>",
12392
+ description: "Pass the previous response's nextCursor unchanged. Omit for newest submissions. Replaces --page/--perPage."
12393
+ },
12394
+ {
12395
+ name: "--sourceId <id>",
12396
+ description: "Filter submissions to this source; keep the same filter while following cursors."
12397
+ }
12398
+ ]),
12399
+ legacyCommand("count-inbound-submissions", ["inbound", "submission", "count"], [], [
12400
+ {
12401
+ name: "--sourceId <id>",
12402
+ description: "Optional source filter. Returns an independently calculated exact totalCount, not a list snapshot."
12403
+ }
12404
+ ]),
11724
12405
  legacyCommand("get-inbound-submission", ["inbound", "submission", "get"]),
11725
12406
  legacyCommand("save-inbound-mapping", ["inbound", "mapping", "save"]),
11726
12407
  legacyCommand("save-inbound-automation-config", [
@@ -11862,48 +12543,323 @@ var completionWords = () => {
11862
12543
  words.add("completion");
11863
12544
  return [...words].sort();
11864
12545
  };
11865
- var createCompletionScript = (shell) => {
11866
- const words = completionWords().join(" ");
11867
- switch (shell) {
11868
- case "bash":
11869
- return `# bash completion for bb
11870
- _bb() {
11871
- COMPREPLY=( $(compgen -W '${words}' -- "${"$"}{COMP_WORDS[COMP_CWORD]}") )
11872
- }
11873
- complete -F _bb bb
11874
- `;
11875
- case "zsh":
11876
- return `#compdef bb
11877
- _bb() {
11878
- local -a commands
11879
- commands=(${words})
11880
- _describe 'bb command' commands
11881
- }
11882
- compdef _bb bb
11883
- `;
11884
- case "fish":
11885
- return completionWords().map((word) => `complete -c bb -f -a '${word}'`).join(`
11886
- `).concat(`
11887
- `);
11888
- case "powershell":
11889
- return `Register-ArgumentCompleter -Native -CommandName bb -ScriptBlock {
11890
- param($wordToComplete)
11891
- '${words}'.Split(' ') | Where-Object { $_ -like "$wordToComplete*" }
11892
- }
11893
- `;
12546
+ var createCompletionScript = (shell) => {
12547
+ const words = completionWords().join(" ");
12548
+ switch (shell) {
12549
+ case "bash":
12550
+ return `# bash completion for bb
12551
+ _bb() {
12552
+ COMPREPLY=( $(compgen -W '${words}' -- "${"$"}{COMP_WORDS[COMP_CWORD]}") )
12553
+ }
12554
+ complete -F _bb bb
12555
+ `;
12556
+ case "zsh":
12557
+ return `#compdef bb
12558
+ _bb() {
12559
+ local -a commands
12560
+ commands=(${words})
12561
+ _describe 'bb command' commands
12562
+ }
12563
+ compdef _bb bb
12564
+ `;
12565
+ case "fish":
12566
+ return completionWords().map((word) => `complete -c bb -f -a '${word}'`).join(`
12567
+ `).concat(`
12568
+ `);
12569
+ case "powershell":
12570
+ return `Register-ArgumentCompleter -Native -CommandName bb -ScriptBlock {
12571
+ param($wordToComplete)
12572
+ '${words}'.Split(' ') | Where-Object { $_ -like "$wordToComplete*" }
12573
+ }
12574
+ `;
12575
+ }
12576
+ };
12577
+
12578
+ // src/cli.ts
12579
+ var SUPPLIER_ANALYTICS_SORT_IDS = [
12580
+ "name",
12581
+ "gstRegistered",
12582
+ "status",
12583
+ "phone",
12584
+ "email",
12585
+ "updatedAt",
12586
+ "createdAt"
12587
+ ];
12588
+ function parsePositiveIntFlag(value, flagName) {
12589
+ if (value === undefined)
12590
+ return;
12591
+ const parsed = Number.parseInt(String(value), 10);
12592
+ if (!Number.isFinite(parsed) || parsed <= 0) {
12593
+ throw new Error(`${flagName} must be a positive integer.`);
12594
+ }
12595
+ return parsed;
12596
+ }
12597
+ var COMPANY_SORT_FIELDS = ["name", "createdAt"];
12598
+ var COMPANY_SORT_DIRECTIONS = ["asc", "desc"];
12599
+ var COMPANY_PAGE_FLAGS = [
12600
+ "page",
12601
+ "perPage",
12602
+ "pageSize",
12603
+ "page-size",
12604
+ "per-page",
12605
+ "page_size",
12606
+ "per_page"
12607
+ ];
12608
+ var rejectBareFlag = (flags, key) => {
12609
+ if (flags[key] === true) {
12610
+ throw new Error(`--${key} requires a value.`);
12611
+ }
12612
+ };
12613
+ var rejectCompanyPageFlags = (flags, command) => {
12614
+ for (const key of COMPANY_PAGE_FLAGS) {
12615
+ if (flags[key] !== undefined) {
12616
+ throw new Error(`--${key} is not supported by company ${command}; use --limit and --cursor for list or company count for totals.`);
12617
+ }
12618
+ }
12619
+ };
12620
+ var parseCompanyLimit = (value) => {
12621
+ if (value === undefined)
12622
+ return 20;
12623
+ const parsed = Number(String(value));
12624
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) {
12625
+ throw new Error("--limit must be an integer from 1 to 100.");
12626
+ }
12627
+ return parsed;
12628
+ };
12629
+ var parseCompanySortBy = (value) => {
12630
+ if (value === undefined)
12631
+ return "createdAt";
12632
+ if (!COMPANY_SORT_FIELDS.includes(value)) {
12633
+ throw new Error(`Invalid --sortBy "${value}". Use one of: ${COMPANY_SORT_FIELDS.join(", ")}.`);
12634
+ }
12635
+ return value;
12636
+ };
12637
+ var parseCompanySortDir = (value) => {
12638
+ if (value === undefined)
12639
+ return "desc";
12640
+ if (!COMPANY_SORT_DIRECTIONS.includes(value)) {
12641
+ throw new Error(`Invalid --sortDir "${value}". Use one of: ${COMPANY_SORT_DIRECTIONS.join(", ")}.`);
12642
+ }
12643
+ return value;
12644
+ };
12645
+ var parseCompanyDirection = (value) => {
12646
+ if (value === undefined)
12647
+ return "forward";
12648
+ if (value !== "forward" && value !== "backward") {
12649
+ throw new Error('--direction must be "forward" or "backward".');
12650
+ }
12651
+ return value;
12652
+ };
12653
+ var parseCompanyListFlags = (flags) => {
12654
+ rejectCompanyPageFlags(flags, "list");
12655
+ for (const key of [
12656
+ "name",
12657
+ "createdBy",
12658
+ "createdByIds",
12659
+ "active",
12660
+ "limit",
12661
+ "cursor",
12662
+ "direction",
12663
+ "sortBy",
12664
+ "sortDir"
12665
+ ]) {
12666
+ rejectBareFlag(flags, key);
12667
+ }
12668
+ const cursor = getFlag(flags, "cursor");
12669
+ const direction = parseCompanyDirection(getFlag(flags, "direction"));
12670
+ if (direction === "backward" && cursor === undefined) {
12671
+ throw new Error("--direction backward requires --cursor <token>.");
12672
+ }
12673
+ if (cursor === "")
12674
+ throw new Error("--cursor requires a non-empty token.");
12675
+ return {
12676
+ name: getFlag(flags, "name"),
12677
+ createdBy: getFlag(flags, "createdBy"),
12678
+ createdByIds: parseCommaSeparatedIds(getFlag(flags, "createdByIds")),
12679
+ active: parseOptionalBoolFlag(flags, "active"),
12680
+ limit: parseCompanyLimit(getFlag(flags, "limit")),
12681
+ cursor,
12682
+ direction,
12683
+ sortBy: parseCompanySortBy(getFlag(flags, "sortBy")),
12684
+ sortDir: parseCompanySortDir(getFlag(flags, "sortDir"))
12685
+ };
12686
+ };
12687
+ var parseCompanyCountFlags = (flags) => {
12688
+ rejectCompanyPageFlags(flags, "count");
12689
+ for (const key of [
12690
+ "name",
12691
+ "createdBy",
12692
+ "createdByIds",
12693
+ "active",
12694
+ "limit",
12695
+ "cursor",
12696
+ "direction",
12697
+ "sortBy",
12698
+ "sortDir"
12699
+ ]) {
12700
+ rejectBareFlag(flags, key);
12701
+ }
12702
+ if (flags.limit !== undefined || flags.cursor !== undefined || flags.direction !== undefined) {
12703
+ throw new Error("company count does not accept list pagination flags.");
12704
+ }
12705
+ if (flags.sortBy !== undefined || flags.sortDir !== undefined) {
12706
+ throw new Error("company count does not accept list sorting flags.");
12707
+ }
12708
+ return {
12709
+ name: getFlag(flags, "name"),
12710
+ createdBy: getFlag(flags, "createdBy"),
12711
+ createdByIds: parseCommaSeparatedIds(getFlag(flags, "createdByIds")),
12712
+ active: parseOptionalBoolFlag(flags, "active")
12713
+ };
12714
+ };
12715
+ var PROJECT_SORT_FIELDS = [
12716
+ "name",
12717
+ "createdAt",
12718
+ "status",
12719
+ "companyName",
12720
+ "wonAt"
12721
+ ];
12722
+ var PROJECT_SORT_DIRECTIONS = ["asc", "desc"];
12723
+ var PROJECT_PAGE_FLAGS = [
12724
+ "page",
12725
+ "perPage",
12726
+ "pageSize",
12727
+ "page-size",
12728
+ "per-page",
12729
+ "page_size",
12730
+ "per_page"
12731
+ ];
12732
+ var rejectProjectPageFlags = (flags, command) => {
12733
+ for (const key of PROJECT_PAGE_FLAGS) {
12734
+ if (flags[key] !== undefined) {
12735
+ throw new Error(`--${key} is not supported by project ${command}; use --limit and --cursor for list or project count for totals.`);
12736
+ }
12737
+ }
12738
+ };
12739
+ var parseProjectLimit = (value) => {
12740
+ if (value === undefined)
12741
+ return 20;
12742
+ const parsed = Number(String(value));
12743
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100) {
12744
+ throw new Error("--limit must be an integer from 1 to 100.");
12745
+ }
12746
+ return parsed;
12747
+ };
12748
+ var parseProjectSortBy = (value) => {
12749
+ if (value === undefined)
12750
+ return "createdAt";
12751
+ if (!PROJECT_SORT_FIELDS.includes(value)) {
12752
+ throw new Error(`Invalid --sortBy "${value}". Use one of: ${PROJECT_SORT_FIELDS.join(", ")}.`);
12753
+ }
12754
+ return value;
12755
+ };
12756
+ var parseProjectSortDir = (value) => {
12757
+ if (value === undefined)
12758
+ return "desc";
12759
+ if (!PROJECT_SORT_DIRECTIONS.includes(value)) {
12760
+ throw new Error(`Invalid --sortDir "${value}". Use one of: ${PROJECT_SORT_DIRECTIONS.join(", ")}.`);
11894
12761
  }
12762
+ return value;
11895
12763
  };
11896
-
11897
- // src/cli.ts
11898
- function parsePositiveIntFlag(value, flagName) {
12764
+ var parseProjectWonWithinDays = (value) => {
11899
12765
  if (value === undefined)
11900
12766
  return;
11901
- const parsed = Number.parseInt(String(value), 10);
11902
- if (!Number.isFinite(parsed) || parsed <= 0) {
11903
- throw new Error(`${flagName} must be a positive integer.`);
12767
+ const parsed = Number(value);
12768
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > 3650) {
12769
+ throw new Error("--wonWithinDays must be an integer from 1 to 3650.");
11904
12770
  }
11905
12771
  return parsed;
11906
- }
12772
+ };
12773
+ var parseProjectDirection = (value) => {
12774
+ if (value === undefined)
12775
+ return "forward";
12776
+ if (value !== "forward" && value !== "backward") {
12777
+ throw new Error('--direction must be "forward" or "backward".');
12778
+ }
12779
+ return value;
12780
+ };
12781
+ var parseProjectListFlags = (flags) => {
12782
+ rejectProjectPageFlags(flags, "list");
12783
+ for (const key of [
12784
+ "companyId",
12785
+ "name",
12786
+ "status",
12787
+ "statuses",
12788
+ "active",
12789
+ "wonFrom",
12790
+ "wonTo",
12791
+ "wonWithinDays",
12792
+ "limit",
12793
+ "cursor",
12794
+ "direction",
12795
+ "sortBy",
12796
+ "sortDir"
12797
+ ]) {
12798
+ rejectBareFlag(flags, key);
12799
+ }
12800
+ const cursor = getFlag(flags, "cursor");
12801
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
12802
+ if (direction === "backward" && cursor === undefined) {
12803
+ throw new Error("--direction backward requires --cursor <token>.");
12804
+ }
12805
+ if (cursor === "")
12806
+ throw new Error("--cursor requires a non-empty token.");
12807
+ return {
12808
+ companyId: getFlag(flags, "companyId"),
12809
+ name: getFlag(flags, "name"),
12810
+ status: parseOptionalExtendedProjectStatus(getFlag(flags, "status")),
12811
+ statuses: parseCommaSeparatedProjectStatuses(getFlag(flags, "statuses")),
12812
+ active: parseOptionalBoolFlag(flags, "active"),
12813
+ wonFrom: parseOptionalIsoDateFlag(getFlag(flags, "wonFrom"), "--wonFrom"),
12814
+ wonTo: parseOptionalIsoDateFlag(getFlag(flags, "wonTo"), "--wonTo", {
12815
+ endOfDay: true
12816
+ }),
12817
+ wonWithinDays: parseProjectWonWithinDays(getFlag(flags, "wonWithinDays")),
12818
+ limit: parseProjectLimit(getFlag(flags, "limit")),
12819
+ cursor,
12820
+ direction,
12821
+ sortBy: parseProjectSortBy(getFlag(flags, "sortBy")),
12822
+ sortDir: parseProjectSortDir(getFlag(flags, "sortDir"))
12823
+ };
12824
+ };
12825
+ var parseProjectCountFlags = (flags) => {
12826
+ rejectProjectPageFlags(flags, "count");
12827
+ for (const key of [
12828
+ "companyId",
12829
+ "name",
12830
+ "status",
12831
+ "statuses",
12832
+ "active",
12833
+ "wonFrom",
12834
+ "wonTo",
12835
+ "wonWithinDays",
12836
+ "limit",
12837
+ "cursor",
12838
+ "direction",
12839
+ "sortBy",
12840
+ "sortDir"
12841
+ ]) {
12842
+ rejectBareFlag(flags, key);
12843
+ }
12844
+ if (flags.limit !== undefined || flags.cursor !== undefined || flags.direction !== undefined) {
12845
+ throw new Error("project count does not accept list pagination flags.");
12846
+ }
12847
+ if (flags.sortBy !== undefined || flags.sortDir !== undefined) {
12848
+ throw new Error("project count does not accept list sorting flags.");
12849
+ }
12850
+ return {
12851
+ companyId: getFlag(flags, "companyId"),
12852
+ name: getFlag(flags, "name"),
12853
+ status: parseOptionalExtendedProjectStatus(getFlag(flags, "status")),
12854
+ statuses: parseCommaSeparatedProjectStatuses(getFlag(flags, "statuses")),
12855
+ active: parseOptionalBoolFlag(flags, "active"),
12856
+ wonFrom: parseOptionalIsoDateFlag(getFlag(flags, "wonFrom"), "--wonFrom"),
12857
+ wonTo: parseOptionalIsoDateFlag(getFlag(flags, "wonTo"), "--wonTo", {
12858
+ endOfDay: true
12859
+ }),
12860
+ wonWithinDays: parseProjectWonWithinDays(getFlag(flags, "wonWithinDays"))
12861
+ };
12862
+ };
11907
12863
  function parseOptionalBoolFlag(flags, key) {
11908
12864
  const v = flags[key];
11909
12865
  if (v === undefined || v === true)
@@ -11954,28 +12910,72 @@ function parseCommercialRejectionProjectStatus(flags) {
11954
12910
  throw new Error("--projectStatusOnCommercialRejection requires PITCH or LOST.");
11955
12911
  }
11956
12912
  const status = String(raw).toUpperCase();
11957
- if (status === ProjectStatus.PITCH || status === ProjectStatus.LOST) {
12913
+ if (status === ProjectStatus2.PITCH || status === ProjectStatus2.LOST) {
11958
12914
  return status;
11959
12915
  }
11960
12916
  throw new Error("--projectStatusOnCommercialRejection must be PITCH or LOST.");
11961
12917
  }
11962
- function parseListBillsFlags(flags) {
12918
+ var rejectRetiredPageFlags = (flags) => {
12919
+ for (const key of PROJECT_PAGE_FLAGS) {
12920
+ if (flags[key] !== undefined)
12921
+ throw new Error(`--${key} is retired; use --limit/--cursor, or the matching count command for totals.`);
12922
+ }
12923
+ };
12924
+ var parseBillFilterFlags = (flags) => {
11963
12925
  const statusRaw = getFlag(flags, "status");
11964
12926
  const createdByIdsRaw = getFlag(flags, "createdByIds");
11965
12927
  const searchRaw = getFlag(flags, "search");
11966
- const sortByRaw = getFlag(flags, "sortBy");
11967
- const sortDirRaw = getFlag(flags, "sortDir");
11968
12928
  return {
11969
12929
  projectId: getFlag(flags, "projectId"),
11970
12930
  budgetId: getFlag(flags, "budgetId"),
11971
12931
  statuses: parseCommaSeparatedBillStatuses(statusRaw !== undefined ? String(statusRaw) : undefined),
11972
12932
  search: searchRaw !== undefined ? String(searchRaw) : undefined,
11973
12933
  isClaimable: parseOptionalBoolFlag(flags, "isClaimable"),
11974
- createdByIds: parseCommaSeparatedIds(createdByIdsRaw !== undefined ? String(createdByIdsRaw) : undefined),
12934
+ createdByIds: parseCommaSeparatedIds(createdByIdsRaw !== undefined ? String(createdByIdsRaw) : undefined)
12935
+ };
12936
+ };
12937
+ var parseQuotationFilterFlags = (flags) => {
12938
+ const createdByRaw = getFlag(flags, "createdBy") ?? getFlag(flags, "requesterIds");
12939
+ const searchRaw = getFlag(flags, "search");
12940
+ return {
12941
+ projectId: getFlag(flags, "projectId"),
12942
+ budgetId: getFlag(flags, "budgetId"),
12943
+ supplierId: getFlag(flags, "supplierId"),
12944
+ status: parseCommaSeparatedQuotationStatuses(getFlag(flags, "status")),
12945
+ createdBy: parseCommaSeparatedIds(createdByRaw),
12946
+ search: searchRaw !== undefined ? String(searchRaw).trim() || undefined : undefined
12947
+ };
12948
+ };
12949
+ function parseListBillsFlags(flags) {
12950
+ rejectRetiredPageFlags(flags);
12951
+ for (const key of ["limit", "cursor", "direction", "sortBy", "sortDir"])
12952
+ rejectBareFlag(flags, key);
12953
+ const sortByRaw = getFlag(flags, "sortBy");
12954
+ const sortDirRaw = getFlag(flags, "sortDir");
12955
+ const cursor = getFlag(flags, "cursor");
12956
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
12957
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
12958
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
12959
+ if (direction === "backward" && !cursor)
12960
+ throw new Error("--direction backward requires --cursor <token>.");
12961
+ return {
12962
+ ...parseBillFilterFlags(flags),
11975
12963
  sortBy: parseOptionalBillListSortBy(sortByRaw !== undefined ? String(sortByRaw) : undefined),
11976
12964
  sortDir: parseOptionalBillListSortDir(sortDirRaw !== undefined ? String(sortDirRaw) : undefined),
11977
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
11978
- pageSize: parsePositiveIntFlag(getFlag(flags, "pageSize"), "--pageSize")
12965
+ limit: parseProjectLimit(getFlag(flags, "limit")),
12966
+ cursor,
12967
+ direction
12968
+ };
12969
+ }
12970
+ function parseCountBillsFlags(flags) {
12971
+ rejectRetiredPageFlags(flags);
12972
+ for (const key of ["limit", "cursor", "direction", "sortBy", "sortDir"]) {
12973
+ if (flags[key] !== undefined)
12974
+ throw new Error(`bill count does not accept --${key}; totals use filters only.`);
12975
+ }
12976
+ return {
12977
+ ...parseBillFilterFlags(flags),
12978
+ includeMetrics: parseOptionalBoolFlag(flags, "includeMetrics") ?? false
11979
12979
  };
11980
12980
  }
11981
12981
  var SUPPLIER_SORT_FIELDS = [
@@ -11996,25 +12996,89 @@ function parseOptionalSupplierSortBy(raw) {
11996
12996
  }
11997
12997
  return raw;
11998
12998
  }
11999
- function parseListSuppliersFlags(flags) {
12000
- const createdByRaw = getFlag(flags, "createdBy");
12001
- const statusRaw = getFlag(flags, "status");
12002
- const supplierTagsRaw = getFlag(flags, "supplierTags");
12003
- const sortBy = parseOptionalSupplierSortBy(getFlag(flags, "sortBy"));
12004
- const sortDirRaw = getFlag(flags, "sortDir");
12005
- const sortDir = parseOptionalBillListSortDir(sortDirRaw !== undefined ? String(sortDirRaw) : undefined);
12999
+ var parseSupplierFilters = (flags) => {
13000
+ for (const key of [
13001
+ "name",
13002
+ "createdBy",
13003
+ "gstRegistered",
13004
+ "status",
13005
+ "supplierTags",
13006
+ "active"
13007
+ ])
13008
+ rejectBareFlag(flags, key);
12006
13009
  return {
12007
13010
  name: getFlag(flags, "name"),
12008
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
12009
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
12010
- sort: sortBy !== undefined || sortDir !== undefined ? [{ id: sortBy ?? "createdAt", desc: (sortDir ?? "desc") === "desc" }] : undefined,
12011
- createdBy: parseCommaSeparatedIds(createdByRaw !== undefined ? String(createdByRaw) : undefined),
12012
- gstRegistered: parseOptionalBoolFlag(flags, "gstRegistered"),
12013
- status: parseCommaSeparatedSupplierStatuses(statusRaw !== undefined ? String(statusRaw) : undefined),
12014
- supplierTags: parseCommaSeparatedIds(supplierTagsRaw !== undefined ? String(supplierTagsRaw) : undefined),
12015
- active: parseOptionalBoolFlag(flags, "active")
13011
+ createdBy: parseCommaSeparatedIds(getFlag(flags, "createdBy")),
13012
+ gstRegistered: parseOptionalBoolFlag(flags, "gstRegistered") ?? null,
13013
+ status: parseCommaSeparatedSupplierStatuses(getFlag(flags, "status")),
13014
+ supplierTags: parseCommaSeparatedIds(getFlag(flags, "supplierTags")),
13015
+ active: parseOptionalBoolFlag(flags, "active") ?? true
12016
13016
  };
12017
- }
13017
+ };
13018
+ var rejectSupplierPageFlags = (flags) => {
13019
+ for (const key of PROJECT_PAGE_FLAGS) {
13020
+ if (flags[key] !== undefined)
13021
+ throw new Error(`--${key} is retired for suppliers; use --limit/--cursor or supplier count for totals.`);
13022
+ }
13023
+ };
13024
+ var parseListSuppliersFlags = (flags) => {
13025
+ rejectSupplierPageFlags(flags);
13026
+ for (const key of [
13027
+ "limit",
13028
+ "cursor",
13029
+ "direction",
13030
+ "sort",
13031
+ "sortBy",
13032
+ "sortDir"
13033
+ ])
13034
+ rejectBareFlag(flags, key);
13035
+ const limit = getFlag(flags, "limit") === undefined ? 10 : parseProjectLimit(getFlag(flags, "limit"));
13036
+ const cursor = getFlag(flags, "cursor");
13037
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
13038
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
13039
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
13040
+ if (direction === "backward" && !cursor)
13041
+ throw new Error("--direction backward requires --cursor <token>.");
13042
+ const sortRaw = getFlag(flags, "sort");
13043
+ let sort;
13044
+ if (sortRaw !== undefined) {
13045
+ if (flags.sortBy !== undefined || flags.sortDir !== undefined)
13046
+ throw new Error("Use --sort or --sortBy/--sortDir, not both.");
13047
+ const parsed = parseJsonFlag(sortRaw, "--sort");
13048
+ if (!Array.isArray(parsed) || parsed.length > 8)
13049
+ throw new Error("--sort must be a JSON array of at most eight {id, desc} entries.");
13050
+ sort = parsed.map((item) => {
13051
+ if (typeof item !== "object" || item === null || !("id" in item) || !("desc" in item) || typeof item.id !== "string" || typeof item.desc !== "boolean" || Object.keys(item).some((key) => key !== "id" && key !== "desc"))
13052
+ throw new Error("Invalid --sort entry; use {id, desc}.");
13053
+ const id = parseOptionalSupplierSortBy(item.id);
13054
+ if (!id)
13055
+ throw new Error("Invalid --sort field.");
13056
+ return { id, desc: item.desc };
13057
+ });
13058
+ } else {
13059
+ const sortBy = parseOptionalSupplierSortBy(getFlag(flags, "sortBy"));
13060
+ const sortDir = parseOptionalBillListSortDir(getFlag(flags, "sortDir"));
13061
+ sort = [
13062
+ { id: sortBy ?? "createdAt", desc: (sortDir ?? "desc") === "desc" }
13063
+ ];
13064
+ }
13065
+ return { ...parseSupplierFilters(flags), limit, cursor, direction, sort };
13066
+ };
13067
+ var parseCountSuppliersFlags = (flags) => {
13068
+ rejectSupplierPageFlags(flags);
13069
+ for (const key of [
13070
+ "limit",
13071
+ "cursor",
13072
+ "direction",
13073
+ "sort",
13074
+ "sortBy",
13075
+ "sortDir"
13076
+ ]) {
13077
+ if (flags[key] !== undefined)
13078
+ throw new Error(`supplier count does not accept --${key}; totals use filters only.`);
13079
+ }
13080
+ return parseSupplierFilters(flags);
13081
+ };
12018
13082
  function parseDashboardQueryFlags(flags) {
12019
13083
  const roleRaw = getFlag(flags, "role");
12020
13084
  const timeFrameRaw = getFlag(flags, "timeFrame");
@@ -12049,7 +13113,8 @@ pending-approver assignment. On FORBIDDEN, stop and report the identity, role,
12049
13113
  command, and target; do not switch keys or try a lower-level procedure.
12050
13114
 
12051
13115
  Sensitive workflow changes: approval decisions/requests, entity status updates,
12052
- estimate emails, customer invoices, integration retries, mark-budget-won,
13116
+ estimate emails, customer invoices, integration retries and QuickBooks outcome
13117
+ verification, mark-budget-won,
12053
13118
  submit-quotation, and supplier approval request side effects require an
12054
13119
  interactive CONFIRM prompt and abort in non-interactive shells.
12055
13120
  AI agents must inspect the entity first, summarize the exact entity, target
@@ -12060,15 +13125,20 @@ Budget mutations return a conflict while an estimate email is queued,
12060
13125
  processing, or awaiting provider reconciliation.
12061
13126
 
12062
13127
  Budgets
12063
- list-budgets [--projectId] [--name] [--status CSV] [--createdBy] [--dateFrom] [--dateTo] [--sortBy] [--sortDir] [--page] [--perPage] [--summary]
13128
+ list-budgets [--projectId] [--name] [--status CSV] [--createdBy] [--dateFrom] [--dateTo] [--sortBy lastActivityAt|createdAt|status|budget|date|createdBy] [--sortDir] [--limit 1-100] [--cursor <token>] [--direction forward|backward] [--summary]
13129
+ Returns { items, pageInfo }; follow nextCursor/previousCursor with the same filters and sort. --page/--perPage are rejected.
13130
+ count-budgets [same filter flags as list-budgets] totalCount plus status and active facets (each ignores its own filter)
12064
13131
  get-budget <id> Budget + line items
12065
13132
  get-budget-items <id> Line items only
12066
13133
  get-budget-details <id> Full detail (margins, suppliers, etc.)
12067
- get-budget-categories
12068
- get-budget-versions <budgetId> [--limit <1-50>] [--cursor <versionId> | --all]
13134
+ get-budget-categories [--q] [--limit 1-100] [--cursor <token>]
13135
+ Active categories, name A\u2013Z. Returns { items, pageInfo }; follow nextCursor with the same --q.
13136
+ get-budget-versions <budgetId> [--limit <1-50>] [--cursor <token> | --all]
12069
13137
  rename-budget-version <versionId> --name <text>
12070
13138
  restore-budget-version <versionId> Replace current budget data and return it to DRAFT.
12071
13139
  update-budget-status <id> <status> [--projectManagerId <id>] [--markProjectWon true|false] [--projectStatusOnCommercialRejection PITCH|LOST]
13140
+ APPROVED requires a current Lead/Admin role, regardless of budget management permissions. Ordinary users must request approval.
13141
+ Material edits and generic status changes invalidate older pending budget requests; decide only a fresh request for the content reviewed.
12072
13142
  Budget-changing commands require interactive "yes" confirmation when the current budget is ESTIMATE_CREATED, ESTIMATE_SENT, ESTIMATE_ACCEPTED, ESTIMATE_REJECTED, or ESTIMATE_CLOSED.
12073
13143
  ESTIMATE_ACCEPTED requires win proof, auto-marks PITCH or LOST projects WON, requires --projectManagerId when the project has no PM, and auto-creates the Asana project/section when enabled.
12074
13144
  ESTIMATE_REJECTED requires --projectStatusOnCommercialRejection when rejecting the only accepted/closed budget on a commercial project.
@@ -12078,7 +13148,7 @@ Budgets
12078
13148
  update-budget --payload '<json>' (budget.updateBudget; must include id)
12079
13149
  delete-budget <budgetId>
12080
13150
  create-budget-approval <budgetId> (also queues approval request emails)
12081
- create-estimate <budgetId> (creates QuickBooks estimate; supports preflight updates)
13151
+ create-estimate <budgetId> (queues QuickBooks estimate creation; budget is read-only until it settles; supports preflight updates)
12082
13152
  send-estimate-to-contact-person --payload '<json>' Send using the web contact-person workflow; payload includes recipient fields plus HTML content/signature.
12083
13153
  add-budget-items --budgetId --items '[{"itemId":"\u2026","quantity":1,"markup":30},\u2026]'
12084
13154
  update-budget-item --id <budgetItemId> [--description plain-text] [--note plain-text] [--quantity] [--markup] [--cost] [--unitPrice] [--isFreeOfCharge true|false] [--gstInclusive true|false] [--gstOutOfScope true|false]
@@ -12086,7 +13156,7 @@ Budgets
12086
13156
  replace-budget-item --budgetItemId <id> --replacementItemId <activeCatalogueItemId>
12087
13157
  approve-unavailable-item-exception --budgetItemId <id> --reason <text> Lead/Admin only; reason must be 5-1000 characters.
12088
13158
  reorder-budget-items <budgetId> --itemIds <csv>
12089
- update-budget-item-supplier --budgetItemId --supplierId
13159
+ update-budget-item-supplier --budgetItemId --supplierId Material supplier/GST edit: supersedes pending approvals and applies the standard budget reset policy; unchanged supplier is a no-op.
12090
13160
  create-budget-category --name <text>
12091
13161
  update-budget-category --id --name
12092
13162
  delete-budget-category <id>
@@ -12100,8 +13170,11 @@ Budgets
12100
13170
  upload-budget-attachment <budgetId> <filePath> Presigned S3 upload + confirm
12101
13171
 
12102
13172
  Bills
12103
- list-bills [--projectId] [--budgetId] [--status CSV] [--search <text>] [--isClaimable true|false] [--createdByIds <csv>] [--sortBy createdAt|amount|status] [--sortDir asc|desc] [--page] [--pageSize]
13173
+ list-bills [--projectId] [--budgetId] [--status CSV] [--search <text>] [--isClaimable true|false] [--createdByIds <csv>] [--sortBy createdAt|amount|status] [--sortDir asc|desc] [--limit 1-100] [--cursor <token>] [--direction forward|backward]
13174
+ Returns { items, pageInfo }; pass nextCursor with --direction forward or previousCursor with --direction backward, keeping the same filters and sort. --page/--pageSize/--perPage are rejected.
12104
13175
  list-claims same flags as list-bills; only reimbursable claims (ignores --isClaimable)
13176
+ count-bills [--projectId] [--budgetId] [--status CSV] [--search <text>] [--isClaimable true|false] [--createdByIds <csv>] [--includeMetrics true|false] totalCount, status facets (ignore --status) and optional amount metrics; no pagination/sort flags
13177
+ count-claims same flags as count-bills; only reimbursable claims
12105
13178
  Both lists return summaries; use get-bill-details <billId> for full detail.
12106
13179
  isClaimable differentiates the shared bill/claim records: false = bill, true = claim.
12107
13180
  Create supplier bills from approved quotations from the same supplier and project. Use quotationAllocations plus lineAmounts when quotations split one bill line. Eligible exemptions require explicit --quotation-exempt intent; claims do not consume or link quotations.
@@ -12112,7 +13185,7 @@ Bills
12112
13185
  validate-bill-selection --payload '<json>' [--quotationIds <csv> | --quotationId <id>] Validate an approved supplier and approved quotation coverage for each supplier-bill line. --quotationId is legacy single-quotation syntax.
12113
13186
  link-bill-quotations <billId> (--payload '<json>' | --quotationIds <csv>) Admin-only source-link repair; payload.quotationAllocations supports same-line splits.
12114
13187
  stage-bill-attachment <projectId> <filePath...> [--file <path>] [--files <csv>] Upload files before create-bill; returns attachment JSON for attachments/paymentProofAttachments
12115
- cleanup-staged-bill-attachments <projectId> --keys <csv> Delete unattached staged bill/claim uploads.
13188
+ cleanup-staged-bill-attachments <projectId> --keys <csv> Queue cleanup of unattached staged uploads; returns operation IDs.
12116
13189
  update-bill --payload '<json>' (bill.update; must include id)
12117
13190
  update-bill-payment-evidence --payload '<json>' Replace paymentReference/paymentProofAttachments for an already-paid bill.
12118
13191
  delete-bill <billId>
@@ -12123,23 +13196,25 @@ Bills
12123
13196
  patch-bill-payment <billId> [--paymentTrackingUrl] [--paymentReference] [--quickbooksBillId] [--paymentDate <ISO>]
12124
13197
  Clear a field: --clearPaymentTrackingUrl true | --clearPaymentReference true | --clearQuickbooksBillId true | --clearPaymentDate true
12125
13198
  patch-bill-invoice-number <billId> <invoiceNumber>
12126
- get-bill-attachments <billId>
13199
+ get-bill-attachments <billId> [--limit 1-50] [--cursor <token>]
13200
+ Newest first. Returns { items, pageInfo }; follow nextCursor.
12127
13201
  upload-bill-attachment <billId> <filePath...> [--file <path>] [--files <csv>] Attach invoice/supporting files as BILL attachments to any bill status, including PAID bills
12128
13202
  attach-bill-payment-receipt <billId> <filePath...> [--file <path>] [--files <csv>] Attach Wise/payment-proof PDFs as BILL_PAYMENT_PROOF attachments
12129
13203
  attach-bill-documents <billId> [--invoice <path>] [--invoices <csv>] [--paymentProof|--payment-proof|--proof <path>] [--paymentProofs|--payment-proofs|--proofs <csv>] Attach invoice and/or payment proof files with separate BB attachment types
12130
13204
  get-bill-details <billId>
12131
13205
 
12132
13206
  Quotations
12133
- list-quotations [--projectId] [--budgetId] [--supplierId] [--status CSV] [--createdBy CSV] [--search] [--page] [--perPage]
12134
- Legacy aliases --requesterIds and --pageSize remain supported.
13207
+ list-quotations [--projectId] [--budgetId] [--supplierId] [--status CSV] [--createdBy CSV] [--search] [--limit 1-100] [--cursor <token>] [--direction forward|backward]
13208
+ Newest first; returns { items, pageInfo }. Follow nextCursor/previousCursor with the same filters. --page/--pageSize/--perPage are rejected; --requesterIds remains an alias for --createdBy.
13209
+ count-quotations [--projectId] [--budgetId] [--supplierId] [--status CSV] [--createdBy CSV] [--search] [--includeMetrics true|false] totalCount, status facets (ignore --status) and optional amount metrics
12135
13210
  get-quotation <quotationId>
12136
13211
  upload-quotation-attachment <projectId> <filePath> Upload one PDF, JPEG, or PNG up to 20MB; returns attachment JSON for a quotation payload.
12137
- cleanup-staged-quotation-attachments <projectId> --keys <csv> Delete unattached staged quotation uploads.
13212
+ cleanup-staged-quotation-attachments <projectId> --keys <csv> Queue cleanup of unattached quotation uploads; returns operation IDs.
12138
13213
  create-quotation --payload '<json>' (quotation.createDraft; requires amountWithoutGst, gstAmount, and lineAmounts [{budgetItemId, amount}] for multiple budgetItemIds; amounts before GST must sum to amountWithoutGst)
12139
13214
  update-quotation --payload '<json>' Update a DRAFT or REJECTED quotation; same allocation payload plus id; use lineAmounts [{budgetItemId, amount}] for every selected item.
12140
13215
  delete-quotation <quotationId>
12141
13216
  submit-quotation <quotationId> [--overBudgetOverrideReason <text>]
12142
- approve-quotation <quotationId> [--overBudgetOverrideReason <text>]
13217
+ approve-quotation <quotationId> [--overBudgetOverrideReason <text>] (assigned pending request/current Admin; concurrent revision or allocation changes conflict)
12143
13218
  reject-quotation <quotationId> --reason <text>
12144
13219
  download-quotation-pdf <quotationId> [original|staff|final]
12145
13220
 
@@ -12148,8 +13223,10 @@ Customer invoices
12148
13223
  list-eligible-customer-invoice-budgets <projectId> Accepted budgets that are ready and still have invoice coverage remaining, including estimate total, invoiced/remaining percentages, and available amount.
12149
13224
  preview-customer-invoice-amount <budgetId> <percentage-of-estimate> Preview amount before GST, GST, and total for a percentage of the original estimate. The percentage cannot exceed the budget's remaining coverage.
12150
13225
  list-available-qbo-customer-invoices <budgetId> List unlinked QuickBooks invoices available for this accepted or closed estimate before previewing and importing one.
12151
- list-customer-invoices [budgetId] [--projectId <id>] [--companyIds CSV] [--projectIds CSV] [--budgetIds CSV] [--createdByIds CSV] [--status CSV] [--search] [--sortBy createdAt|status|totalInvoiceAmount|approvalExpiresAt] [--sortDir asc|desc] [--page] [--perPage]
13226
+ list-customer-invoices [budgetId] [--projectId <id>] [--companyIds CSV] [--projectIds CSV] [--budgetIds CSV] [--createdByIds CSV] [--status CSV] [--search] [--sortBy createdAt|status|totalInvoiceAmount|approvalExpiresAt] [--sortDir asc|desc] [--limit 1-100] [--cursor <token>] [--direction forward|backward]
13227
+ Returns { items, pageInfo }; follow nextCursor/previousCursor with the same filters and sort. --page/--perPage are rejected.
12152
13228
  No scope flag lists invoices globally. --projectId uses the project scope. A positional budgetId is a backward-compatible shortcut for --budgetIds.
13229
+ count-customer-invoices [budgetId] [same filter flags as list-customer-invoices] totalCount, lifecycle status facets (ignore --status) and amount metrics; no paging or sort flags
12153
13230
  get-customer-invoice <batchId>
12154
13231
  get-customer-invoice-email-context <batchId> Contact, required CC, budget, and project context for composing an invoice email.
12155
13232
  preview-qbo-customer-invoice <budgetId> <qboInvoiceId> Preview an invoice selected from list-available-qbo-customer-invoices; works on accepted/closed estimates.
@@ -12174,20 +13251,28 @@ Approvals
12174
13251
  reject-supplier <supplierId> --reason <text> (also sends requester reply email)
12175
13252
 
12176
13253
  Companies & projects
12177
- list-companies [--name] [--createdBy] [--active true|false] [--page] [--perPage]
12178
- Omit --active to include both active and inactive companies.
13254
+ list-companies [--name] [--createdBy] [--createdByIds CSV] [--active true|false] [--sortBy name|createdAt] [--sortDir asc|desc] [--limit 1-100] [--cursor] [--direction forward|backward]
13255
+ Returns { items, pageInfo }; pass nextCursor with direction forward or previousCursor with direction backward. Omit --cursor to restart.
13256
+ count-companies [--name] [--createdBy] [--createdByIds CSV] [--active true|false]
13257
+ Returns independent { totalCount, counts: { active: { true, false } } }; no pagination or sorting flags.
12179
13258
  get-company <id>
12180
- search-existing-companies --query <name> [--page] (BB + QuickBooks, read-only)
13259
+ search-existing-companies --query <name> [--limit 1-50] [--cursor <token>] (BB + QuickBooks, read-only; follow pageInfo.nextCursor)
12181
13260
  use-existing-company --companyId <bbId> and/or --quickbooksId <qboId> [--website <url>] [--confirmDistinct true]
12182
13261
  Supply both IDs to explicitly connect existing records with different names.
12183
13262
  Reuses both records, creating only the missing counterpart. Website is needed only for a QBO import without one.
12184
13263
  create-company --name --website <url> [--confirmDistinct true]
12185
13264
  Exact existing companies are reused. Similar names require explicit separate-entity confirmation.
12186
- update-company --payload '<json>' (company.updateCompany; must include id, name, website)
13265
+ update-company --payload '<json>' (saves BB fields; returns queued QBO operation ID, not completion)
13266
+ get-company-quickbooks-sync-status <companyId> (saved status only; no provider call or polling)
12187
13267
  delete-company <id>
12188
- list-projects [--companyId] [--name] [--status] [--active true|false] [--page] [--perPage]
12189
- Omit --active to include both active and inactive projects.
12190
- list-project-hubs [--q] [--status CSV] [--page] [--perPage] [--sortBy startDate|name|companyName|status] [--sortDir asc|desc]
13268
+ list-projects [--companyId] [--name] [--status|--statuses CSV] [--active true|false] [--wonFrom ISO] [--wonTo ISO] [--wonWithinDays N] [--limit 1-100] [--cursor] [--direction forward|backward] [--sortBy name|createdAt|status|companyName|wonAt] [--sortDir asc|desc]
13269
+ Returns { items, pageInfo }; pass nextCursor with direction forward or previousCursor with direction backward. Legacy --page/--perPage are rejected.
13270
+ --sortBy wonAt orders by when the project was marked WON (unwon projects last). Each project includes wonAt, wonById, wonBy and wonAtEstimated (true for backfilled dates).
13271
+ count-projects [--companyId] [--name] [--status|--statuses CSV] [--active true|false] [--wonFrom ISO] [--wonTo ISO] [--wonWithinDays N]
13272
+ Returns independent { totalCount, counts: { status, active: { true, false } } }; no pagination or sorting flags.
13273
+ list-project-hubs [--q] [--status CSV] [--sortBy startDate|name|companyName|status] [--sortDir asc|desc] [--limit 1-100] [--cursor <token>] [--direction forward|backward]
13274
+ Returns { items, pageInfo }; follow nextCursor/previousCursor with the same filters and sort. --page/--perPage are rejected.
13275
+ count-project-hubs [--q] [--status CSV] totalCount plus status facet
12191
13276
  List active projects that have a Project Hub integration.
12192
13277
  get-project <id>
12193
13278
  Budget overview totalRevenue includes Estimate Accepted and Estimate Closed budgets only.
@@ -12209,9 +13294,10 @@ Contacts
12209
13294
  update-contact-person --payload '<json>' (contactPerson.updateContactPerson; must include id)
12210
13295
 
12211
13296
  Suppliers & items
12212
- list-suppliers [--name] [--page] [--perPage] [--sortBy createdAt|updatedAt|name|email|phone|gstRegistered|status|active] [--sortDir asc|desc] [--createdBy <csv>] [--gstRegistered true|false] [--status CSV] [--supplierTags <csv>] [--active true|false]
12213
- Defaults: --active true, --perPage 10, --sortBy createdAt, --sortDir desc (same as the web list); pass --active false for archived suppliers.
12214
- create-supplier --payload '<json>' (supplier.createSupplier; auto-requests approval + emails when status is PENDING_APPROVAL)
13297
+ list-suppliers [--name] [--limit 1-100] [--cursor <token>] [--direction forward|backward] [--sortBy createdAt|updatedAt|name|email|phone|gstRegistered|status|active] [--sortDir asc|desc | --sort JSON] [--createdBy <csv>] [--gstRegistered true|false] [--status CSV] [--supplierTags <csv>] [--active true|false]
13298
+ count-suppliers [--name] [--createdBy <csv>] [--gstRegistered true|false] [--status CSV] [--supplierTags <csv>] [--active true|false] (independent totals/facets; no pagination/sort flags)
13299
+ Defaults: --active true, --limit 10, --sortBy createdAt, --sortDir desc (same as the web list); pass --active false for archived suppliers.
13300
+ create-supplier --payload '<json>' (supplier.createSupplier; archived-name recreation is Admin-only; auto-requests approval + emails when status is PENDING_APPROVAL)
12215
13301
  update-supplier --payload '<json>' (authenticated; normal-user edits require server-enforced approval)
12216
13302
  delete-suppliers --ids <csv> (supplier.deleteSuppliers; admin; archives/deactivates supplier items)
12217
13303
  reactivate-suppliers --ids <csv> (supplier.reactivateSuppliers; admin; reactivates supplier items)
@@ -12224,20 +13310,29 @@ Suppliers & items
12224
13310
  archive-items --ids <csv> (item.archiveItems; Budget Builder only; max 100; users can archive their own items, leads/admins any item; open estimates retain snapshot lines and require resolution before send/acceptance)
12225
13311
  delete-item <id> (item.deleteItem; admin; archives in Budget Builder and makes the item inactive in QuickBooks; blocked while used by an open estimate)
12226
13312
  get-supplier-details <supplierId>
12227
- get-supplier-analytics [--name] [--page] [--perPage] [--timeFrame ALL|LAST_YEAR|\u2026] (admin)
12228
- list-items [--name] [--page] [--perPage] [--includeQuickBooksDeactivationPending true]
13313
+ get-supplier-analytics [--name] [--status CSV] [--gstRegistered true|false] [--createdBy <text>] [--timeFrame ALL|LAST_YEAR|LAST_6_MONTHS|LAST_3_MONTHS|CUSTOM] [--startDate <ISO> --endDate <ISO>] [--sortBy name|gstRegistered|status|phone|email|updatedAt|createdAt] [--sortDir asc|desc] [--limit 1-100] [--cursor <token>] [--direction forward|backward]
13314
+ Admin. Returns { items, pageInfo }; follow nextCursor/previousCursor with the same filters and sort. --page/--perPage are rejected.
13315
+ count-supplier-analytics [--name] [--status] [--gstRegistered] [--createdBy] [--timeFrame] [--startDate --endDate] Admin. totalCount for the same filters
13316
+ list-items [--name] [--includeQuickBooksDeactivationPending true] [--sortBy createdAt|updatedAt|name|cost|itemCategory|supplier] [--sortDir asc|desc] [--limit 1-100] [--cursor <token>] [--direction forward|backward]
13317
+ Returns { items, pageInfo }; follow nextCursor/previousCursor with the same filters and sort. --page/--perPage are rejected.
13318
+ count-items [same filter flags as list-items] total plus category and supplier facets
12229
13319
  get-item <id>
12230
- list-item-categories [--page] [--perPage]
13320
+ get-item-quickbooks-sync-status <itemId> (saved status only; no provider call or polling)
13321
+ list-item-categories [--name] [--sortBy createdAt|name|itemCount|createdBy] [--sortDir asc|desc] [--limit 1-100] [--cursor <token>] [--direction forward|backward]
13322
+ Active categories only; returns { items, pageInfo }. Follow nextCursor/previousCursor with the same filters and sort. --page/--perPage are rejected.
13323
+ count-item-categories [--name] totalCount of active categories
12231
13324
  create-item-category --name (admin)
12232
13325
  update-item-category --id --name (authenticated; applies immediately)
12233
13326
  delete-item-categories --ids <csv> (itemCategory.deleteItemCategories; admin; empty categories only)
12234
13327
 
12235
13328
  Dashboard & users
12236
13329
  whoami Show the user identity and role for the active API key.
12237
- list-users
13330
+ list-users [--q] [--limit 1-100] [--cursor <token>]
13331
+ Name A\u2013Z. Returns { items, pageInfo }; follow nextCursor with the same --q. --page/--perPage are rejected.
13332
+ count-users [--q] totalCount for the same search
12238
13333
  create-user --email <email> [--name <name>] [--role ${userRolesForHelp.join("|")}] (admin; API-only service identity with no Google sign-in)
12239
13334
  create-api-key --name <label> [--userId <userId>] (defaults to the caller; admin required for another user; raw key is shown once)
12240
- list-api-keys [--userId <userId>] (defaults to the caller; admin required for another user)
13335
+ list-api-keys [--userId <userId>] [--limit 1-100] [--cursor <token>] (newest first, { items, pageInfo }; defaults to the caller; admin required for another user)
12241
13336
  revoke-api-key <apiKeyId> [--userId <userId>] (defaults to the caller; admin required for another user)
12242
13337
  get-user-performance [--userId] (admin)
12243
13338
  get-dashboard [--userId] [--role BD|CREATOR|INSIDE_SALES|ALL] [--deals ALL|SUCCESSFUL|LOST] [--timeFrame] [--startDate] [--endDate] (admin)
@@ -12247,12 +13342,30 @@ Dashboard & users
12247
13342
  get-financial-overview [same optional flags as dashboard; role uses BudgetRole enum] (admin)
12248
13343
 
12249
13344
  Errors
12250
- get-recent-errors [--page] [--perPage] [--severity] [--status] (admin)
13345
+ get-recent-errors [--cursor] [--direction forward|backward] [--limit] [--severity] [--status] [--timeFrame] (admin)
13346
+ count-errors [--severity] [--status] [--timeFrame] (admin)
12251
13347
  get-error-metrics (admin)
12252
13348
 
12253
13349
  Automation (admin)
12254
- list-integration-operations [--search] [--destination <provider,...>] [--status <PENDING|PROCESSING|FAILED|COMPLETED,...>] [--page] [--perPage]
12255
- retry-integration-operation <operationId> [--confirmExternalStateReconciled true|false] [--outboundEmailResolution ACCEPTED|NOT_ACCEPTED_RETRY] [--providerMessageId <id>]
13350
+ list-integration-operations [--search] [--destination <provider,...>] [--status <PENDING|PROCESSING|FAILED|COMPLETED,...>] [--limit 1-100] [--cursor <token>] [--direction forward|backward]
13351
+ Newest first; returns { items, pageInfo }. Follow nextCursor/previousCursor with the same filters. --page/--perPage are rejected.
13352
+ count-integration-operations [same filter flags] totalCount plus provider filter options
13353
+ get-integration-operation <operationId> (one job: who queued it, what it is for, saved progress, Retry state)
13354
+ retry-integration-operation <operationId> [--confirmExternalStateReconciled true|false] [--outboundEmailResolution ACCEPTED|NOT_ACCEPTED_RETRY] [--providerMessageId <id>] (QuickBooks jobs with a saved attempt check QuickBooks first: applied changes finish without resending, unapplied ones are sent again)
13355
+ reconcile-resource-operation <operationId> (admin: check a QuickBooks invoice/company/item job outcome without retrying; same check Retry runs first)
13356
+
13357
+ Webhooks (admin)
13358
+ list-webhook-event-types (event catalogue: type, group, live|planned)
13359
+ list-webhook-endpoints
13360
+ get-webhook-endpoint <endpointId>
13361
+ create-webhook-endpoint --name <name> --url <https-url> --events <type|prefix.*|*,...> [--enabled true|false] (prints the signing secret once)
13362
+ update-webhook-endpoint <endpointId> [--name] [--url] [--events <...>] [--enabled true|false]
13363
+ delete-webhook-endpoint <endpointId>
13364
+ rotate-webhook-secret <endpointId> [--previousSecretTtlHours <0-168>] (prints the new secret once)
13365
+ send-webhook-test-event <endpointId>
13366
+ list-webhook-deliveries [--endpointId] [--status PENDING|SUCCEEDED|FAILED] [--eventType] [--cursor] [--limit]
13367
+ get-webhook-delivery <deliveryId> (attempts with response codes)
13368
+ replay-webhook-delivery <deliveryId> (same eventId, new attempt chain)
12256
13369
 
12257
13370
  Historical / benchmarks
12258
13371
  get-approved-budgets [--projectId] [--limit] [--minRevenue] [--maxRevenue]
@@ -12594,6 +13707,8 @@ Waiting for approval\u2026
12594
13707
  });
12595
13708
  }
12596
13709
  try {
13710
+ if (flags.q === true)
13711
+ throw new CliRuntimeError("USAGE", "--q needs a search value (--q <text> or --q=<text>). Use -q or --quiet to hide logs.");
12597
13712
  if (runtime.env.BB_API_KEY?.trim()) {
12598
13713
  configureAccessToken(undefined);
12599
13714
  } else {
@@ -12625,21 +13740,49 @@ Waiting for approval\u2026
12625
13740
  await whoAmI();
12626
13741
  break;
12627
13742
  }
12628
- case "list-budgets": {
13743
+ case "list-budgets":
13744
+ case "count-budgets": {
12629
13745
  const statusStr = getFlag(flags, "status");
12630
13746
  const statuses = parseCommaSeparatedBudgetStatuses(statusStr !== undefined ? String(statusStr) : undefined);
12631
- await listBudgets({
13747
+ const filters = {
12632
13748
  projectId: getFlag(flags, "projectId"),
12633
13749
  name: getFlag(flags, "name"),
12634
13750
  statuses,
12635
13751
  createdBy: getFlag(flags, "createdBy"),
12636
13752
  dateFrom: getFlag(flags, "dateFrom"),
12637
13753
  dateTo: getFlag(flags, "dateTo"),
12638
- active: parseBudgetActiveFlag(flags),
13754
+ active: parseBudgetActiveFlag(flags)
13755
+ };
13756
+ rejectRetiredPageFlags(flags);
13757
+ if (cmd === "count-budgets") {
13758
+ for (const key of [
13759
+ "limit",
13760
+ "cursor",
13761
+ "direction",
13762
+ "sortBy",
13763
+ "sortDir"
13764
+ ]) {
13765
+ if (flags[key] !== undefined)
13766
+ throw new Error(`count-budgets does not accept --${key}; totals use filters only.`);
13767
+ }
13768
+ await countBudgets(filters);
13769
+ break;
13770
+ }
13771
+ for (const key of ["limit", "cursor", "direction", "sortBy", "sortDir"])
13772
+ rejectBareFlag(flags, key);
13773
+ const cursor = getFlag(flags, "cursor");
13774
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
13775
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
13776
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
13777
+ if (direction === "backward" && !cursor)
13778
+ throw new Error("--direction backward requires --cursor <token>.");
13779
+ await listBudgets({
13780
+ ...filters,
13781
+ limit: parseProjectLimit(getFlag(flags, "limit")),
13782
+ cursor,
13783
+ direction,
12639
13784
  sortBy: getFlag(flags, "sortBy"),
12640
13785
  sortDir: getFlag(flags, "sortDir"),
12641
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
12642
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
12643
13786
  includeDetails: flags.summary === true ? false : parseOptionalBoolFlag(flags, "includeDetails") ?? true
12644
13787
  });
12645
13788
  break;
@@ -12692,7 +13835,19 @@ Waiting for approval\u2026
12692
13835
  break;
12693
13836
  }
12694
13837
  case "get-budget-categories": {
12695
- await getBudgetCategories();
13838
+ rejectRetiredPageFlags(flags);
13839
+ for (const key of ["limit", "cursor"])
13840
+ rejectBareFlag(flags, key);
13841
+ if (flags.direction !== undefined)
13842
+ throw new Error("get-budget-categories pages forward only; follow pageInfo.nextCursor with --cursor.");
13843
+ const cursor = getFlag(flags, "cursor");
13844
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
13845
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
13846
+ await getBudgetCategories({
13847
+ q: getFlag(flags, "q"),
13848
+ limit: parseProjectLimit(getFlag(flags, "limit")),
13849
+ cursor
13850
+ });
12696
13851
  break;
12697
13852
  }
12698
13853
  case "get-budget-versions": {
@@ -12704,7 +13859,7 @@ Waiting for approval\u2026
12704
13859
  throw new CliRuntimeError("USAGE", "--limit must be an integer from 1 to 50.");
12705
13860
  }
12706
13861
  if (flags.cursor === true || flags.cursor === "") {
12707
- throw new CliRuntimeError("USAGE", "--cursor requires a non-empty version ID.");
13862
+ throw new CliRuntimeError("USAGE", "--cursor requires the nextCursor token from the previous page.");
12708
13863
  }
12709
13864
  await getBudgetVersions(id, {
12710
13865
  limit,
@@ -12985,6 +14140,14 @@ Waiting for approval\u2026
12985
14140
  });
12986
14141
  break;
12987
14142
  }
14143
+ case "count-bills": {
14144
+ await countBills(parseCountBillsFlags(flags));
14145
+ break;
14146
+ }
14147
+ case "count-claims": {
14148
+ await countBills({ ...parseCountBillsFlags(flags), isClaimable: true });
14149
+ break;
14150
+ }
12988
14151
  case "create-bill-approval": {
12989
14152
  const id = positional[0];
12990
14153
  if (!id)
@@ -13185,7 +14348,19 @@ Waiting for approval\u2026
13185
14348
  const id = positional[0];
13186
14349
  if (!id)
13187
14350
  throw new Error("get-bill-attachments requires <billId>");
13188
- await getBillAttachments(id);
14351
+ rejectRetiredPageFlags(flags);
14352
+ for (const key of ["limit", "cursor"])
14353
+ rejectBareFlag(flags, key);
14354
+ if (flags.direction !== undefined)
14355
+ throw new Error("get-bill-attachments pages forward only; follow pageInfo.nextCursor with --cursor.");
14356
+ const cursor = getFlag(flags, "cursor");
14357
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
14358
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
14359
+ const limitFlag = getFlag(flags, "limit");
14360
+ const limit = limitFlag === undefined ? undefined : Number(limitFlag);
14361
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 1 || limit > 50))
14362
+ throw new Error("--limit must be an integer from 1 to 50.");
14363
+ await getBillAttachments({ id, limit, cursor });
13189
14364
  break;
13190
14365
  }
13191
14366
  case "stage-bill-attachment": {
@@ -13278,18 +14453,32 @@ Waiting for approval\u2026
13278
14453
  break;
13279
14454
  }
13280
14455
  case "list-quotations": {
13281
- const statusRaw = getFlag(flags, "status");
13282
- const createdByRaw = getFlag(flags, "createdBy") ?? getFlag(flags, "requesterIds");
13283
- const perPageRaw = getFlag(flags, "perPage") ?? getFlag(flags, "pageSize");
14456
+ rejectRetiredPageFlags(flags);
14457
+ for (const key of ["limit", "cursor", "direction"])
14458
+ rejectBareFlag(flags, key);
14459
+ const cursor = getFlag(flags, "cursor");
14460
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
14461
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
14462
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
14463
+ if (direction === "backward" && !cursor)
14464
+ throw new Error("--direction backward requires --cursor <token>.");
13284
14465
  await listQuotations({
13285
- projectId: getFlag(flags, "projectId"),
13286
- budgetId: getFlag(flags, "budgetId"),
13287
- supplierId: getFlag(flags, "supplierId"),
13288
- status: parseCommaSeparatedQuotationStatuses(statusRaw),
13289
- createdBy: parseCommaSeparatedIds(createdByRaw),
13290
- search: getFlag(flags, "search"),
13291
- page: getFlag(flags, "page") !== undefined ? Number(getFlag(flags, "page")) : undefined,
13292
- perPage: perPageRaw !== undefined ? Number(perPageRaw) : undefined
14466
+ ...parseQuotationFilterFlags(flags),
14467
+ limit: parseProjectLimit(getFlag(flags, "limit")),
14468
+ cursor,
14469
+ direction
14470
+ });
14471
+ break;
14472
+ }
14473
+ case "count-quotations": {
14474
+ rejectRetiredPageFlags(flags);
14475
+ for (const key of ["limit", "cursor", "direction"]) {
14476
+ if (flags[key] !== undefined)
14477
+ throw new Error(`count-quotations does not accept --${key}; totals use filters only.`);
14478
+ }
14479
+ await countQuotations({
14480
+ ...parseQuotationFilterFlags(flags),
14481
+ includeMetrics: parseOptionalBoolFlag(flags, "includeMetrics") ?? false
13293
14482
  });
13294
14483
  break;
13295
14484
  }
@@ -13411,7 +14600,8 @@ Waiting for approval\u2026
13411
14600
  await listAvailableQboCustomerInvoices(budgetId);
13412
14601
  break;
13413
14602
  }
13414
- case "list-customer-invoices": {
14603
+ case "list-customer-invoices":
14604
+ case "count-customer-invoices": {
13415
14605
  const positionalBudgetId = positional[0];
13416
14606
  const projectId = getFlag(flags, "projectId");
13417
14607
  const projectIds = parseCommaSeparatedIds(getFlag(flags, "projectIds"));
@@ -13423,17 +14613,44 @@ Waiting for approval\u2026
13423
14613
  ...parseCommaSeparatedIds(getFlag(flags, "budgetIds")) ?? []
13424
14614
  ];
13425
14615
  const statusRaw = getFlag(flags, "status");
13426
- const sortDir = parseOptionalBillListSortDir(getFlag(flags, "sortDir"));
13427
- await listCustomerInvoices({
14616
+ const filters = {
13428
14617
  scope: projectId ? { type: "project", projectId } : { type: "global" },
13429
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page") ?? 1,
13430
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage") ?? 20,
13431
14618
  q: getFlag(flags, "search") ?? "",
13432
14619
  statuses: parseCommaSeparatedCustomerInvoiceStatuses(statusRaw !== undefined ? String(statusRaw) : undefined),
13433
14620
  companyIds: parseCommaSeparatedIds(getFlag(flags, "companyIds")),
13434
14621
  projectIds,
13435
14622
  budgetIds: budgetIds.length > 0 ? [...new Set(budgetIds)] : undefined,
13436
- createdByIds: parseCommaSeparatedIds(getFlag(flags, "createdByIds")),
14623
+ createdByIds: parseCommaSeparatedIds(getFlag(flags, "createdByIds"))
14624
+ };
14625
+ rejectRetiredPageFlags(flags);
14626
+ if (cmd === "count-customer-invoices") {
14627
+ for (const key of [
14628
+ "limit",
14629
+ "cursor",
14630
+ "direction",
14631
+ "sortBy",
14632
+ "sortDir"
14633
+ ]) {
14634
+ if (flags[key] !== undefined)
14635
+ throw new Error(`count-customer-invoices does not accept --${key}; totals use filters only.`);
14636
+ }
14637
+ await countCustomerInvoices(filters);
14638
+ break;
14639
+ }
14640
+ for (const key of ["limit", "cursor", "direction", "sortBy", "sortDir"])
14641
+ rejectBareFlag(flags, key);
14642
+ const cursor = getFlag(flags, "cursor");
14643
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
14644
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
14645
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
14646
+ if (direction === "backward" && !cursor)
14647
+ throw new Error("--direction backward requires --cursor <token>.");
14648
+ const sortDir = parseOptionalBillListSortDir(getFlag(flags, "sortDir"));
14649
+ await listCustomerInvoices({
14650
+ ...filters,
14651
+ limit: parseProjectLimit(getFlag(flags, "limit")),
14652
+ cursor,
14653
+ direction,
13437
14654
  sortBy: parseCustomerInvoiceSortBy(getFlag(flags, "sortBy")),
13438
14655
  sortDir: sortDir ?? "desc"
13439
14656
  });
@@ -13612,13 +14829,11 @@ Waiting for approval\u2026
13612
14829
  break;
13613
14830
  }
13614
14831
  case "list-companies": {
13615
- await listCompanies({
13616
- name: getFlag(flags, "name"),
13617
- createdBy: getFlag(flags, "createdBy"),
13618
- active: parseOptionalBoolFlag(flags, "active"),
13619
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
13620
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage")
13621
- });
14832
+ await listCompanies(parseCompanyListFlags(flags));
14833
+ break;
14834
+ }
14835
+ case "count-companies": {
14836
+ await countCompanies(parseCompanyCountFlags(flags));
13622
14837
  break;
13623
14838
  }
13624
14839
  case "get-company": {
@@ -13645,7 +14860,17 @@ Waiting for approval\u2026
13645
14860
  const query = getFlag(flags, "query");
13646
14861
  if (!query)
13647
14862
  throw new Error("search-existing-companies requires --query <name>");
13648
- await searchExistingCompanies(query, parsePositiveIntFlag(getFlag(flags, "page"), "--page"));
14863
+ rejectRetiredPageFlags(flags);
14864
+ for (const key of ["limit", "cursor"])
14865
+ rejectBareFlag(flags, key);
14866
+ const cursor = getFlag(flags, "cursor");
14867
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
14868
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
14869
+ const limitFlag = getFlag(flags, "limit");
14870
+ const limit = limitFlag === undefined ? undefined : Number(limitFlag);
14871
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 1 || limit > 50))
14872
+ throw new Error("--limit must be an integer from 1 to 50.");
14873
+ await searchExistingCompanies({ query, limit, cursor });
13649
14874
  break;
13650
14875
  }
13651
14876
  case "use-existing-company": {
@@ -13678,6 +14903,13 @@ Waiting for approval\u2026
13678
14903
  await updateCompanyFromPayload(raw);
13679
14904
  break;
13680
14905
  }
14906
+ case "get-company-quickbooks-sync-status": {
14907
+ const id = positional[0];
14908
+ if (!id)
14909
+ throw new Error("get-company-quickbooks-sync-status requires <companyId>");
14910
+ await getCompanyQuickBooksSyncStatus(id);
14911
+ break;
14912
+ }
13681
14913
  case "delete-company": {
13682
14914
  const id = positional[0];
13683
14915
  if (!id)
@@ -13686,23 +14918,46 @@ Waiting for approval\u2026
13686
14918
  break;
13687
14919
  }
13688
14920
  case "list-projects": {
13689
- const statusRaw = getFlag(flags, "status");
13690
- await listProjects({
13691
- companyId: getFlag(flags, "companyId"),
13692
- name: getFlag(flags, "name"),
13693
- status: parseOptionalExtendedProjectStatus(statusRaw !== undefined ? String(statusRaw) : undefined),
13694
- active: parseOptionalBoolFlag(flags, "active"),
13695
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
13696
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage")
13697
- });
14921
+ await listProjects(parseProjectListFlags(flags));
13698
14922
  break;
13699
14923
  }
13700
- case "list-project-hubs": {
13701
- await listProjectHubs({
14924
+ case "count-projects": {
14925
+ await countProjects(parseProjectCountFlags(flags));
14926
+ break;
14927
+ }
14928
+ case "list-project-hubs":
14929
+ case "count-project-hubs": {
14930
+ rejectRetiredPageFlags(flags);
14931
+ const filters = {
13702
14932
  q: getFlag(flags, "q"),
13703
- statuses: parseCommaSeparatedProjectStatuses(getFlag(flags, "status")),
13704
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
13705
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
14933
+ statuses: parseCommaSeparatedProjectStatuses(getFlag(flags, "status"))
14934
+ };
14935
+ if (cmd === "count-project-hubs") {
14936
+ for (const key of [
14937
+ "limit",
14938
+ "cursor",
14939
+ "direction",
14940
+ "sortBy",
14941
+ "sortDir"
14942
+ ])
14943
+ if (flags[key] !== undefined)
14944
+ throw new Error(`count-project-hubs does not accept --${key}; totals use filters only.`);
14945
+ await countProjectHubs(filters);
14946
+ break;
14947
+ }
14948
+ for (const key of ["limit", "cursor", "direction", "sortBy", "sortDir"])
14949
+ rejectBareFlag(flags, key);
14950
+ const cursor = getFlag(flags, "cursor");
14951
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
14952
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
14953
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
14954
+ if (direction === "backward" && !cursor)
14955
+ throw new Error("--direction backward requires --cursor <token>.");
14956
+ await listProjectHubs({
14957
+ ...filters,
14958
+ limit: parseProjectLimit(getFlag(flags, "limit")),
14959
+ cursor,
14960
+ direction,
13706
14961
  sortBy: parseOptionalProjectHubSortBy(getFlag(flags, "sortBy")),
13707
14962
  sortDir: parseOptionalBillListSortDir(getFlag(flags, "sortDir"))
13708
14963
  });
@@ -13871,6 +15126,10 @@ Waiting for approval\u2026
13871
15126
  await updateContactPersonFromPayload(raw);
13872
15127
  break;
13873
15128
  }
15129
+ case "count-suppliers": {
15130
+ await countSuppliers(parseCountSuppliersFlags(flags));
15131
+ break;
15132
+ }
13874
15133
  case "list-suppliers": {
13875
15134
  await listSuppliers(parseListSuppliersFlags(flags));
13876
15135
  break;
@@ -13945,22 +15204,117 @@ Waiting for approval\u2026
13945
15204
  await createSupplierTagOption(String(name));
13946
15205
  break;
13947
15206
  }
13948
- case "get-supplier-analytics": {
15207
+ case "get-supplier-analytics":
15208
+ case "count-supplier-analytics": {
15209
+ rejectRetiredPageFlags(flags);
13949
15210
  const tfRaw = getFlag(flags, "timeFrame");
13950
- await getSupplierAnalytics({
15211
+ for (const key of [
15212
+ "name",
15213
+ "status",
15214
+ "gstRegistered",
15215
+ "createdBy",
15216
+ "startDate",
15217
+ "endDate",
15218
+ "timeFrame"
15219
+ ])
15220
+ rejectBareFlag(flags, key);
15221
+ const timeFrame = parseOptionalSupplierAnalyticsTimeFrame(tfRaw !== undefined ? String(tfRaw) : undefined);
15222
+ const startDate = getFlag(flags, "startDate");
15223
+ const endDate = getFlag(flags, "endDate");
15224
+ if ((startDate || endDate) && timeFrame !== "CUSTOM")
15225
+ throw new Error("--startDate/--endDate need --timeFrame CUSTOM; other time frames ignore them.");
15226
+ const statusRaw = getFlag(flags, "status");
15227
+ const status = statusRaw?.split(",").map((value) => value.trim()).filter(Boolean);
15228
+ const badStatus = status?.find((value) => !Object.values(ApprovalStatus2).some((s) => s === value));
15229
+ if (badStatus)
15230
+ throw new Error(`Invalid --status ${badStatus}. Use a comma list of: ${Object.values(ApprovalStatus2).join(", ")}.`);
15231
+ const gstRaw = getFlag(flags, "gstRegistered");
15232
+ if (gstRaw !== undefined && gstRaw !== "true" && gstRaw !== "false")
15233
+ throw new Error("--gstRegistered must be true or false.");
15234
+ const filters = {
13951
15235
  name: getFlag(flags, "name"),
13952
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
13953
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
13954
- timeFrame: parseOptionalSupplierAnalyticsTimeFrame(tfRaw !== undefined ? String(tfRaw) : undefined)
15236
+ timeFrame,
15237
+ status: status?.length ? status : undefined,
15238
+ gstRegistered: gstRaw === undefined ? undefined : gstRaw === "true",
15239
+ createdBy: getFlag(flags, "createdBy"),
15240
+ startDate,
15241
+ endDate
15242
+ };
15243
+ if (cmd === "count-supplier-analytics") {
15244
+ for (const key of [
15245
+ "limit",
15246
+ "cursor",
15247
+ "direction",
15248
+ "sortBy",
15249
+ "sortDir"
15250
+ ])
15251
+ if (flags[key] !== undefined)
15252
+ throw new Error(`count-supplier-analytics does not accept --${key}; totals use filters only.`);
15253
+ await countSupplierAnalytics(filters);
15254
+ break;
15255
+ }
15256
+ for (const key of ["limit", "cursor", "direction", "sortBy", "sortDir"])
15257
+ rejectBareFlag(flags, key);
15258
+ const cursor = getFlag(flags, "cursor");
15259
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
15260
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
15261
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
15262
+ if (direction === "backward" && !cursor)
15263
+ throw new Error("--direction backward requires --cursor <token>.");
15264
+ const sortBy = getFlag(flags, "sortBy");
15265
+ if (sortBy !== undefined && !SUPPLIER_ANALYTICS_SORT_IDS.some((id) => id === sortBy))
15266
+ throw new Error(`--sortBy must be one of: ${SUPPLIER_ANALYTICS_SORT_IDS.join(", ")}.`);
15267
+ await getSupplierAnalytics({
15268
+ ...filters,
15269
+ limit: parseProjectLimit(getFlag(flags, "limit")),
15270
+ cursor,
15271
+ direction,
15272
+ sortBy: SUPPLIER_ANALYTICS_SORT_IDS.find((id) => id === sortBy),
15273
+ sortDir: parseOptionalBillListSortDir(getFlag(flags, "sortDir"))
13955
15274
  });
13956
15275
  break;
13957
15276
  }
13958
- case "list-items": {
13959
- await listItems({
15277
+ case "list-items":
15278
+ case "count-items": {
15279
+ rejectRetiredPageFlags(flags);
15280
+ const filters = {
13960
15281
  includeQuickBooksDeactivationPending: flags.includeQuickBooksDeactivationPending === true ? true : parseOptionalBoolFlag(flags, "includeQuickBooksDeactivationPending"),
13961
- name: getFlag(flags, "name"),
13962
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
13963
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage")
15282
+ name: getFlag(flags, "name")
15283
+ };
15284
+ if (cmd === "count-items") {
15285
+ for (const key of [
15286
+ "limit",
15287
+ "cursor",
15288
+ "direction",
15289
+ "sortBy",
15290
+ "sortDir"
15291
+ ])
15292
+ if (flags[key] !== undefined)
15293
+ throw new Error(`count-items does not accept --${key}; totals use filters only.`);
15294
+ await countItems(filters);
15295
+ break;
15296
+ }
15297
+ for (const key of ["limit", "cursor", "direction", "sortBy", "sortDir"])
15298
+ rejectBareFlag(flags, key);
15299
+ const cursor = getFlag(flags, "cursor");
15300
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
15301
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
15302
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
15303
+ if (direction === "backward" && !cursor)
15304
+ throw new Error("--direction backward requires --cursor <token>.");
15305
+ const sortBy = getFlag(flags, "sortBy");
15306
+ const sortId = INVENTORY_SORT_IDS.find((id) => id === sortBy);
15307
+ if (sortBy !== undefined && !sortId)
15308
+ throw new Error(`Invalid --sortBy "${sortBy}". Use one of: ${INVENTORY_SORT_IDS.join(", ")}.`);
15309
+ const sortDir = getFlag(flags, "sortDir");
15310
+ if (sortDir !== undefined && sortDir !== "asc" && sortDir !== "desc")
15311
+ throw new Error('--sortDir must be "asc" or "desc".');
15312
+ await listItems({
15313
+ ...filters,
15314
+ limit: parseProjectLimit(getFlag(flags, "limit")),
15315
+ cursor,
15316
+ direction,
15317
+ sort: sortId ? [{ id: sortId, desc: sortDir !== "asc" }] : undefined
13964
15318
  });
13965
15319
  break;
13966
15320
  }
@@ -13971,6 +15325,14 @@ Waiting for approval\u2026
13971
15325
  await getItem(id);
13972
15326
  break;
13973
15327
  }
15328
+ case "get-item-quickbooks-sync-status": {
15329
+ const id = positional[0];
15330
+ if (!id) {
15331
+ throw new Error("get-item-quickbooks-sync-status requires <itemId>");
15332
+ }
15333
+ await getItemQuickBooksSyncStatus(id);
15334
+ break;
15335
+ }
13974
15336
  case "create-item": {
13975
15337
  const payloadRaw = getFlag(flags, "payload");
13976
15338
  if (!payloadRaw) {
@@ -14008,10 +15370,45 @@ Waiting for approval\u2026
14008
15370
  await archiveItemsByIds(ids);
14009
15371
  break;
14010
15372
  }
14011
- case "list-item-categories": {
15373
+ case "list-item-categories":
15374
+ case "count-item-categories": {
15375
+ rejectRetiredPageFlags(flags);
15376
+ const filters = { name: getFlag(flags, "name") };
15377
+ if (cmd === "count-item-categories") {
15378
+ for (const key of [
15379
+ "limit",
15380
+ "cursor",
15381
+ "direction",
15382
+ "sortBy",
15383
+ "sortDir"
15384
+ ])
15385
+ if (flags[key] !== undefined)
15386
+ throw new Error(`count-item-categories does not accept --${key}; totals use filters only.`);
15387
+ await countItemCategories(filters);
15388
+ break;
15389
+ }
15390
+ for (const key of ["limit", "cursor", "direction", "sortBy", "sortDir"])
15391
+ rejectBareFlag(flags, key);
15392
+ const cursor = getFlag(flags, "cursor");
15393
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
15394
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
15395
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
15396
+ if (direction === "backward" && !cursor)
15397
+ throw new Error("--direction backward requires --cursor <token>.");
15398
+ const sortByRaw = getFlag(flags, "sortBy");
15399
+ const sortBy = ITEM_CATEGORY_SORT_IDS.find((id) => id === sortByRaw);
15400
+ if (sortByRaw !== undefined && !sortBy)
15401
+ throw new Error(`Invalid --sortBy "${sortByRaw}". Use one of: ${ITEM_CATEGORY_SORT_IDS.join(", ")}.`);
15402
+ const sortDir = getFlag(flags, "sortDir");
15403
+ if (sortDir !== undefined && sortDir !== "asc" && sortDir !== "desc")
15404
+ throw new Error('--sortDir must be "asc" or "desc".');
14012
15405
  await listItemCategories({
14013
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
14014
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage")
15406
+ ...filters,
15407
+ limit: parseProjectLimit(getFlag(flags, "limit")),
15408
+ cursor,
15409
+ direction,
15410
+ sortBy,
15411
+ sortDir
14015
15412
  });
14016
15413
  break;
14017
15414
  }
@@ -14043,8 +15440,29 @@ Waiting for approval\u2026
14043
15440
  await deleteItemCategoriesByIds(ids);
14044
15441
  break;
14045
15442
  }
14046
- case "list-users": {
14047
- await listUsers();
15443
+ case "list-users":
15444
+ case "count-users": {
15445
+ rejectRetiredPageFlags(flags);
15446
+ const q = getFlag(flags, "q");
15447
+ if (cmd === "count-users") {
15448
+ for (const key of ["limit", "cursor"])
15449
+ if (flags[key] !== undefined)
15450
+ throw new Error(`count-users does not accept --${key}; totals use --q only.`);
15451
+ await countUsers({ q });
15452
+ break;
15453
+ }
15454
+ for (const key of ["limit", "cursor"])
15455
+ rejectBareFlag(flags, key);
15456
+ if (flags.direction !== undefined)
15457
+ throw new Error("list-users pages forward only; follow pageInfo.nextCursor with --cursor.");
15458
+ const cursor = getFlag(flags, "cursor");
15459
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
15460
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
15461
+ await listUsers({
15462
+ q,
15463
+ limit: parseProjectLimit(getFlag(flags, "limit")),
15464
+ cursor
15465
+ });
14048
15466
  break;
14049
15467
  }
14050
15468
  case "create-user": {
@@ -14069,7 +15487,23 @@ Waiting for approval\u2026
14069
15487
  break;
14070
15488
  }
14071
15489
  case "list-api-keys": {
14072
- await listApiKeysForUser({ userId: getFlag(flags, "userId") });
15490
+ rejectRetiredPageFlags(flags);
15491
+ for (const key of ["limit", "cursor"])
15492
+ rejectBareFlag(flags, key);
15493
+ if (flags.direction !== undefined)
15494
+ throw new Error("list-api-keys pages forward only; follow pageInfo.nextCursor with --cursor.");
15495
+ const cursor = getFlag(flags, "cursor");
15496
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
15497
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
15498
+ const limitFlag = getFlag(flags, "limit");
15499
+ const limit = limitFlag === undefined ? undefined : Number(limitFlag);
15500
+ if (limit !== undefined && (!Number.isInteger(limit) || limit < 1 || limit > 100))
15501
+ throw new Error("--limit must be an integer from 1 to 100.");
15502
+ await listApiKeysForUser({
15503
+ userId: getFlag(flags, "userId"),
15504
+ limit,
15505
+ cursor
15506
+ });
14073
15507
  break;
14074
15508
  }
14075
15509
  case "revoke-api-key": {
@@ -14114,29 +15548,81 @@ Waiting for approval\u2026
14114
15548
  });
14115
15549
  break;
14116
15550
  }
14117
- case "get-recent-errors": {
14118
- await getRecentErrors({
14119
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page"),
14120
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage"),
15551
+ case "get-recent-errors":
15552
+ case "count-errors": {
15553
+ if (getFlag(flags, "page") !== undefined || getFlag(flags, "perPage") !== undefined) {
15554
+ throw new Error("Error logs now use --cursor and --limit; --page/--perPage are no longer supported.");
15555
+ }
15556
+ const rawTimeFrame = getFlag(flags, "timeFrame");
15557
+ const timeFrame = Object.values(ChartTimeFrame).find((value) => value === rawTimeFrame);
15558
+ if (rawTimeFrame !== undefined && timeFrame === undefined) {
15559
+ throw new Error(`Invalid --timeFrame. Use ${Object.values(ChartTimeFrame).join(", ")}.`);
15560
+ }
15561
+ const filters = {
14121
15562
  severity: parseOptionalErrorSeverity(getFlag(flags, "severity")),
14122
- status: parseOptionalErrorStatus(getFlag(flags, "status"))
14123
- });
15563
+ status: parseOptionalErrorStatus(getFlag(flags, "status")),
15564
+ timeFrame
15565
+ };
15566
+ if (cmd === "count-errors") {
15567
+ await countErrors(filters);
15568
+ } else {
15569
+ const direction = getFlag(flags, "direction");
15570
+ if (direction !== undefined && direction !== "forward" && direction !== "backward") {
15571
+ throw new Error("--direction must be forward or backward.");
15572
+ }
15573
+ await getRecentErrors({
15574
+ ...filters,
15575
+ cursor: getFlag(flags, "cursor"),
15576
+ limit: parsePositiveIntFlag(getFlag(flags, "limit"), "--limit"),
15577
+ direction
15578
+ });
15579
+ }
14124
15580
  break;
14125
15581
  }
14126
15582
  case "get-error-metrics": {
14127
15583
  await getErrorMetrics();
14128
15584
  break;
14129
15585
  }
14130
- case "list-integration-operations": {
14131
- await listIntegrationOperations({
15586
+ case "list-integration-operations":
15587
+ case "count-integration-operations": {
15588
+ rejectRetiredPageFlags(flags);
15589
+ const filters = {
14132
15590
  destination: parseCommaSeparatedIds(getFlag(flags, "destination")),
14133
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page") ?? 1,
14134
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage") ?? 25,
14135
15591
  search: getFlag(flags, "search"),
14136
15592
  status: parseCommaSeparatedIntegrationOperationStatuses(getFlag(flags, "status"))
15593
+ };
15594
+ if (cmd === "count-integration-operations") {
15595
+ for (const key of ["limit", "cursor", "direction"])
15596
+ if (flags[key] !== undefined)
15597
+ throw new Error(`count-integration-operations does not accept --${key}; totals use filters only.`);
15598
+ await countIntegrationOperations(filters);
15599
+ break;
15600
+ }
15601
+ for (const key of ["limit", "cursor", "direction"])
15602
+ rejectBareFlag(flags, key);
15603
+ const cursor = getFlag(flags, "cursor");
15604
+ if (cursor === "" || (cursor?.length ?? 0) > 8192)
15605
+ throw new Error("--cursor must be a non-empty opaque token of at most 8192 characters.");
15606
+ const direction = parseProjectDirection(getFlag(flags, "direction"));
15607
+ if (direction === "backward" && !cursor)
15608
+ throw new Error("--direction backward requires --cursor <token>.");
15609
+ const limitRaw = getFlag(flags, "limit");
15610
+ await listIntegrationOperations({
15611
+ ...filters,
15612
+ limit: limitRaw === undefined ? 25 : parseProjectLimit(limitRaw),
15613
+ cursor,
15614
+ direction
14137
15615
  });
14138
15616
  break;
14139
15617
  }
15618
+ case "get-integration-operation": {
15619
+ const operationId = positional[0];
15620
+ if (!operationId) {
15621
+ throw new Error("get-integration-operation requires <operationId>");
15622
+ }
15623
+ await getIntegrationOperation(operationId);
15624
+ break;
15625
+ }
14140
15626
  case "retry-integration-operation": {
14141
15627
  const operationId = positional[0];
14142
15628
  if (!operationId) {
@@ -14149,13 +15635,153 @@ Waiting for approval\u2026
14149
15635
  await retryIntegrationOperation(operationId, parseOptionalBoolFlag(flags, "confirmExternalStateReconciled") ?? false, outboundEmailResolution, getFlag(flags, "providerMessageId"));
14150
15636
  break;
14151
15637
  }
14152
- case "list-inbound-sources": {
14153
- await listInboundSources({
14154
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page") ?? 1,
14155
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage") ?? 25
15638
+ case "list-webhook-event-types": {
15639
+ await listWebhookEventTypes();
15640
+ break;
15641
+ }
15642
+ case "list-webhook-endpoints": {
15643
+ await listWebhookEndpoints();
15644
+ break;
15645
+ }
15646
+ case "get-webhook-endpoint": {
15647
+ const id = positional[0];
15648
+ if (!id)
15649
+ throw new Error("get-webhook-endpoint requires <id>");
15650
+ await getWebhookEndpoint(id);
15651
+ break;
15652
+ }
15653
+ case "create-webhook-endpoint": {
15654
+ const name = getFlag(flags, "name");
15655
+ const url = getFlag(flags, "url");
15656
+ const events = parseCommaSeparatedIds(getFlag(flags, "events"));
15657
+ if (!name || !url || !events?.length)
15658
+ throw new Error("create-webhook-endpoint requires --name, --url and --events");
15659
+ await createWebhookEndpoint({
15660
+ name,
15661
+ url,
15662
+ eventPatterns: events,
15663
+ enabled: parseOptionalBoolFlag(flags, "enabled") ?? true
15664
+ });
15665
+ break;
15666
+ }
15667
+ case "update-webhook-endpoint": {
15668
+ const id = positional[0];
15669
+ if (!id)
15670
+ throw new Error("update-webhook-endpoint requires <id>");
15671
+ const events = parseCommaSeparatedIds(getFlag(flags, "events"));
15672
+ await updateWebhookEndpoint({
15673
+ id,
15674
+ name: getFlag(flags, "name"),
15675
+ url: getFlag(flags, "url"),
15676
+ eventPatterns: events?.length ? events : undefined,
15677
+ enabled: parseOptionalBoolFlag(flags, "enabled")
15678
+ });
15679
+ break;
15680
+ }
15681
+ case "delete-webhook-endpoint": {
15682
+ const id = positional[0];
15683
+ if (!id)
15684
+ throw new Error("delete-webhook-endpoint requires <id>");
15685
+ await deleteWebhookEndpoint(id);
15686
+ break;
15687
+ }
15688
+ case "rotate-webhook-secret": {
15689
+ const id = positional[0];
15690
+ if (!id)
15691
+ throw new Error("rotate-webhook-secret requires <id>");
15692
+ const ttl = getFlag(flags, "previousSecretTtlHours");
15693
+ await rotateWebhookSecret({
15694
+ id,
15695
+ previousSecretTtlHours: ttl === undefined ? undefined : Number.parseInt(ttl, 10)
15696
+ });
15697
+ break;
15698
+ }
15699
+ case "send-webhook-test-event": {
15700
+ const id = positional[0];
15701
+ if (!id)
15702
+ throw new Error("send-webhook-test-event requires <id>");
15703
+ await sendWebhookTestEvent(id);
15704
+ break;
15705
+ }
15706
+ case "list-webhook-deliveries": {
15707
+ const status = getFlag(flags, "status");
15708
+ if (status && status !== "PENDING" && status !== "SUCCEEDED" && status !== "FAILED")
15709
+ throw new Error("--status must be PENDING, SUCCEEDED or FAILED");
15710
+ await listWebhookDeliveries({
15711
+ endpointId: getFlag(flags, "endpointId"),
15712
+ status,
15713
+ eventType: getFlag(flags, "eventType"),
15714
+ cursor: getFlag(flags, "cursor"),
15715
+ limit: parsePositiveIntFlag(getFlag(flags, "limit"), "--limit")
14156
15716
  });
14157
15717
  break;
14158
15718
  }
15719
+ case "get-webhook-delivery": {
15720
+ const id = positional[0];
15721
+ if (!id)
15722
+ throw new Error("get-webhook-delivery requires <id>");
15723
+ await getWebhookDelivery(id);
15724
+ break;
15725
+ }
15726
+ case "replay-webhook-delivery": {
15727
+ const id = positional[0];
15728
+ if (!id)
15729
+ throw new Error("replay-webhook-delivery requires <id>");
15730
+ await replayWebhookDelivery(id);
15731
+ break;
15732
+ }
15733
+ case "reconcile-resource-operation": {
15734
+ const operationId = positional[0];
15735
+ if (!operationId) {
15736
+ throw new Error("reconcile-resource-operation requires <operationId>");
15737
+ }
15738
+ await reconcileResourceOperation(operationId);
15739
+ break;
15740
+ }
15741
+ case "list-inbound-sources":
15742
+ case "count-inbound-sources": {
15743
+ if (flags.page !== undefined || flags.perPage !== undefined)
15744
+ throw new Error("Inbound sources now use --limit/--cursor/--direction; --page/--perPage are no longer supported.");
15745
+ for (const key of ["search", "processingMode", "cursor", "direction"]) {
15746
+ if (flags[key] === true)
15747
+ throw new Error(`--${key} requires a value.`);
15748
+ }
15749
+ const enabled = flags.processingEnabled;
15750
+ if (enabled !== undefined && enabled !== "true" && enabled !== "false")
15751
+ throw new Error("--processingEnabled must be true or false.");
15752
+ const mode = getFlag(flags, "processingMode");
15753
+ const processingMode = Object.values(InboundProcessingMode).find((value) => value === mode);
15754
+ if (mode !== undefined && processingMode === undefined)
15755
+ throw new Error("Invalid --processingMode. Use ACTIVE, COLLECT_ONLY or SHADOW.");
15756
+ const filters = {
15757
+ search: getFlag(flags, "search"),
15758
+ processingEnabled: enabled === undefined ? undefined : enabled === "true",
15759
+ processingMode
15760
+ };
15761
+ if (cmd === "count-inbound-sources") {
15762
+ if (["limit", "cursor", "direction"].some((key) => flags[key] !== undefined))
15763
+ throw new Error("Source counts accept filters only, not pagination flags.");
15764
+ await countInboundSources(filters);
15765
+ } else {
15766
+ const direction = getFlag(flags, "direction") ?? "forward";
15767
+ if (direction !== "forward" && direction !== "backward")
15768
+ throw new Error("--direction must be forward or backward.");
15769
+ const cursor = getFlag(flags, "cursor");
15770
+ if (direction === "backward" && !cursor)
15771
+ throw new Error("Backward source pagination requires --cursor.");
15772
+ const rawLimit = flags.limit;
15773
+ const limit = rawLimit === undefined ? 25 : Number(rawLimit);
15774
+ if (rawLimit === true || !Number.isInteger(limit) || limit < 1 || limit > 100)
15775
+ throw new Error("--limit must be an integer between 1 and 100.");
15776
+ await listInboundSources({
15777
+ ...filters,
15778
+ cursor,
15779
+ direction,
15780
+ limit
15781
+ });
15782
+ }
15783
+ break;
15784
+ }
14159
15785
  case "create-inbound-source": {
14160
15786
  const name = getFlag(flags, "name");
14161
15787
  const externalSourceId = getFlag(flags, "formId");
@@ -14182,13 +15808,20 @@ Waiting for approval\u2026
14182
15808
  break;
14183
15809
  }
14184
15810
  case "list-inbound-submissions": {
15811
+ if (getFlag(flags, "page") !== undefined || getFlag(flags, "perPage") !== undefined) {
15812
+ throw new Error("Inbound submissions now use --limit and --cursor; --page/--perPage are no longer supported.");
15813
+ }
14185
15814
  await listInboundSubmissions({
14186
- page: parsePositiveIntFlag(getFlag(flags, "page"), "--page") ?? 1,
14187
- perPage: parsePositiveIntFlag(getFlag(flags, "perPage"), "--perPage") ?? 25,
15815
+ cursor: getFlag(flags, "cursor"),
15816
+ limit: parsePositiveIntFlag(getFlag(flags, "limit"), "--limit") ?? 25,
14188
15817
  sourceId: getFlag(flags, "sourceId")
14189
15818
  });
14190
15819
  break;
14191
15820
  }
15821
+ case "count-inbound-submissions": {
15822
+ await countInboundSubmissions({ sourceId: getFlag(flags, "sourceId") });
15823
+ break;
15824
+ }
14192
15825
  case "get-inbound-submission": {
14193
15826
  const submissionId = positional[0];
14194
15827
  if (!submissionId) {