@reporting-bi/cli 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +866 -571
  2. package/package.json +32 -32
package/dist/index.js CHANGED
@@ -30,91 +30,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  mod
31
31
  ));
32
32
 
33
- // ../rp-client/dist/types.js
34
- var require_types = __commonJS({
35
- "../rp-client/dist/types.js"(exports2) {
36
- "use strict";
37
- Object.defineProperty(exports2, "__esModule", { value: true });
38
- }
39
- });
40
-
41
- // ../rp-client/dist/cache.js
42
- var require_cache = __commonJS({
43
- "../rp-client/dist/cache.js"(exports2) {
44
- "use strict";
45
- Object.defineProperty(exports2, "__esModule", { value: true });
46
- exports2.ServiceCache = void 0;
47
- var ServiceCache = class {
48
- constructor() {
49
- this.cache = /* @__PURE__ */ new Map();
50
- }
51
- createKey(params3) {
52
- let key = params3.path;
53
- if (params3.params) {
54
- const paramStrings = [];
55
- for (const k of Object.keys(params3.params).sort()) {
56
- paramStrings.push(`${k}=${params3.params[k]}`);
57
- }
58
- if (paramStrings.length > 0) {
59
- key += `?${paramStrings.join("&")}`;
60
- }
61
- }
62
- return key;
63
- }
64
- set(params3, data) {
65
- const key = this.createKey(params3);
66
- this.cache.set(key, data);
67
- }
68
- get(params3) {
69
- const key = this.createKey(params3);
70
- return this.cache.get(key);
71
- }
72
- has(params3) {
73
- const key = this.createKey(params3);
74
- return this.cache.has(key);
75
- }
76
- clear() {
77
- this.cache.clear();
78
- }
79
- };
80
- exports2.ServiceCache = ServiceCache;
81
- }
82
- });
83
-
84
- // ../rp-client/dist/accountingProvider.js
85
- var require_accountingProvider = __commonJS({
86
- "../rp-client/dist/accountingProvider.js"(exports2) {
87
- "use strict";
88
- Object.defineProperty(exports2, "__esModule", { value: true });
89
- exports2.AccountingProvider = void 0;
90
- var AccountingProvider2 = class {
91
- constructor() {
92
- this._a = null;
93
- this._accounting = null;
94
- }
95
- get a() {
96
- return this._a;
97
- }
98
- set a(a) {
99
- this._a = a;
100
- if (this.accounting?.accounting != a) {
101
- this._accounting = null;
102
- }
103
- }
104
- get accounting() {
105
- return this._accounting;
106
- }
107
- set accounting(accounting2) {
108
- if (accounting2) {
109
- this._a = accounting2.accounting ?? null;
110
- }
111
- this._accounting = accounting2;
112
- }
113
- };
114
- exports2.AccountingProvider = AccountingProvider2;
115
- }
116
- });
117
-
118
33
  // ../../.yarn/cache/es-errors-npm-1.3.0-fda0c9b8a8-0a61325670.zip/node_modules/es-errors/type.js
119
34
  var require_type = __commonJS({
120
35
  "../../.yarn/cache/es-errors-npm-1.3.0-fda0c9b8a8-0a61325670.zip/node_modules/es-errors/type.js"(exports2, module2) {
@@ -2628,249 +2543,6 @@ var require_lib = __commonJS({
2628
2543
  }
2629
2544
  });
2630
2545
 
2631
- // ../rp-client/dist/service.js
2632
- var require_service = __commonJS({
2633
- "../rp-client/dist/service.js"(exports2) {
2634
- "use strict";
2635
- var __importDefault = exports2 && exports2.__importDefault || function(mod) {
2636
- return mod && mod.__esModule ? mod : { "default": mod };
2637
- };
2638
- Object.defineProperty(exports2, "__esModule", { value: true });
2639
- exports2.Service = exports2.ServiceAuthException = exports2.ServiceEvent = exports2.ServiceException = exports2.serializeRequestParams = void 0;
2640
- var qs_1 = __importDefault(require_lib());
2641
- var cache_js_1 = require_cache();
2642
- function isObject2(value) {
2643
- return value !== null && typeof value === "object" && !Array.isArray(value);
2644
- }
2645
- function serializeRequestParams(params3) {
2646
- const p = {};
2647
- for (const k in params3) {
2648
- switch (k) {
2649
- case "customParams":
2650
- p["p"] = JSON.stringify(params3.customParams);
2651
- break;
2652
- default:
2653
- p[k] = isObject2(params3[k]) || Array.isArray(params3[k]) ? JSON.stringify(params3[k]) : params3[k];
2654
- break;
2655
- }
2656
- }
2657
- return qs_1.default.stringify(p, { addQueryPrefix: false });
2658
- }
2659
- exports2.serializeRequestParams = serializeRequestParams;
2660
- var ServiceException = class extends Error {
2661
- constructor(args) {
2662
- super(args?.data?.message || "Unknown error");
2663
- Object.assign(this, args);
2664
- }
2665
- };
2666
- exports2.ServiceException = ServiceException;
2667
- var ServiceEvent2 = class extends Event {
2668
- constructor(name, args) {
2669
- super(name);
2670
- Object.assign(this, args);
2671
- }
2672
- };
2673
- exports2.ServiceEvent = ServiceEvent2;
2674
- var ServiceAuthException = class extends Error {
2675
- };
2676
- exports2.ServiceAuthException = ServiceAuthException;
2677
- var Service2 = class extends EventTarget {
2678
- get a() {
2679
- return this.accountingProvider?.a;
2680
- }
2681
- get name() {
2682
- return this.options.name;
2683
- }
2684
- get cache() {
2685
- if (!this._cache)
2686
- this._cache = new cache_js_1.ServiceCache();
2687
- return this._cache;
2688
- }
2689
- constructor(options) {
2690
- super();
2691
- this.accountingProvider = null;
2692
- this.tokenProvider = null;
2693
- this._cache = null;
2694
- this.options = { ...options };
2695
- if (!/^(https?|\/)/.test(this.options.baseUrl)) {
2696
- this.options.baseUrl = `https://${this.options.baseUrl}`;
2697
- }
2698
- }
2699
- createUrl(path9 = "", params3) {
2700
- if (this.options.xdebugSession) {
2701
- params3 = params3 || {};
2702
- params3.XDEBUG_SESSION = this.options.xdebugSession === true ? "1" : this.options.xdebugSession.toString();
2703
- }
2704
- if (this.options.params && Object.keys(this.options.params).length > 0) {
2705
- params3 = { ...this.options.params, ...params3 };
2706
- }
2707
- let queryString = params3 ? serializeRequestParams(params3) : "";
2708
- if (queryString)
2709
- queryString = `?${queryString}`;
2710
- const a = params3 && params3.accounting ? params3.accounting.toString() : this.a ? this.a.toString() : null;
2711
- path9 = path9.replace(/^\//, "");
2712
- const parts = [this.options.baseUrl, a, this.options.name, path9];
2713
- return parts.filter((p) => p != null && p !== "").join("/") + queryString;
2714
- }
2715
- async createHeaders(headers) {
2716
- const defaultHeaders = {};
2717
- if (this.tokenProvider) {
2718
- const token = await this.tokenProvider();
2719
- if (token) {
2720
- defaultHeaders["Authorization"] = `Bearer ${token}`;
2721
- }
2722
- }
2723
- return { ...defaultHeaders, ...headers };
2724
- }
2725
- get credentials() {
2726
- return this.tokenProvider ? void 0 : "include";
2727
- }
2728
- async handleError(response) {
2729
- const contentType = response.headers.get("Content-Type");
2730
- let error48;
2731
- if (contentType?.includes("application/json")) {
2732
- error48 = await response.json();
2733
- error48.status = response.status;
2734
- } else {
2735
- error48 = { status: response.status, message: "Unknown error", error: "unknown" };
2736
- }
2737
- if (this.options.throwExceptions) {
2738
- throw new ServiceException({ data: error48, url: response.url });
2739
- }
2740
- }
2741
- async handleResponse(response, parse3) {
2742
- if (!response.ok) {
2743
- await this.handleError(response);
2744
- return null;
2745
- }
2746
- const contentType = response.headers.get("Content-Type");
2747
- if (contentType?.includes("application/json") && parse3 !== false) {
2748
- return await response.json();
2749
- } else {
2750
- return await response.text();
2751
- }
2752
- }
2753
- async getRaw(path9, params3, options) {
2754
- const url2 = this.createUrl(path9, params3);
2755
- this.dispatchEvent(new ServiceEvent2("beforeRequest", { url: url2, params: params3, options, method: "GET" }));
2756
- return fetch(url2, {
2757
- headers: await this.createHeaders(options?.headers),
2758
- credentials: this.credentials
2759
- });
2760
- }
2761
- async get(path9, params3, options) {
2762
- if (options?.cache) {
2763
- const cachedData = this.cache.get({ path: path9, params: params3 });
2764
- if (cachedData)
2765
- return cachedData;
2766
- }
2767
- const response = await this.getRaw(path9, params3, options);
2768
- const responseData = await this.handleResponse(response, options?.parseResponse);
2769
- if (options?.cache && responseData) {
2770
- this.cache.set({ path: path9, params: params3 }, responseData);
2771
- }
2772
- return responseData;
2773
- }
2774
- async postRaw(path9, data, params3, options) {
2775
- const url2 = this.createUrl(path9, params3);
2776
- this.dispatchEvent(new ServiceEvent2("beforeRequest", { url: url2, params: params3, options, method: "POST" }));
2777
- const json3 = isObject2(data) || Array.isArray(data);
2778
- return fetch(url2, {
2779
- method: "POST",
2780
- body: json3 ? JSON.stringify(data) : data,
2781
- headers: await this.createHeaders({
2782
- ...options?.headers ?? {},
2783
- "Content-Type": `${json3 ? "application/json" : "text/plain"}; charset=UTF-8`
2784
- }),
2785
- credentials: this.credentials
2786
- });
2787
- }
2788
- async post(path9, data, params3, options) {
2789
- const response = await this.postRaw(path9, data, params3, options);
2790
- return this.handleResponse(response);
2791
- }
2792
- async deleteRaw(path9, params3, options) {
2793
- const url2 = this.createUrl(path9, params3);
2794
- this.dispatchEvent(new ServiceEvent2("beforeRequest", { url: url2, params: params3, options, method: "DELETE" }));
2795
- return fetch(url2, {
2796
- method: "DELETE",
2797
- headers: await this.createHeaders(options?.headers),
2798
- credentials: this.credentials
2799
- });
2800
- }
2801
- async delete(path9, params3, options) {
2802
- const response = await this.deleteRaw(path9, params3, options);
2803
- return this.handleResponse(response);
2804
- }
2805
- async putRaw(path9, data, params3, options) {
2806
- const url2 = this.createUrl(path9, params3);
2807
- this.dispatchEvent(new ServiceEvent2("beforeRequest", { url: url2, params: params3, options, method: "PUT" }));
2808
- const json3 = isObject2(data) || Array.isArray(data);
2809
- return fetch(url2, {
2810
- method: "PUT",
2811
- body: json3 ? JSON.stringify(data) : data,
2812
- headers: await this.createHeaders({
2813
- ...options?.headers ?? {},
2814
- "Content-Type": `${json3 ? "application/json" : "text/plain"}; charset=UTF-8`
2815
- }),
2816
- credentials: this.credentials
2817
- });
2818
- }
2819
- async put(path9, data, params3, options) {
2820
- const response = await this.putRaw(path9, data, params3, options);
2821
- return this.handleResponse(response);
2822
- }
2823
- async patchRaw(path9, data, params3, options) {
2824
- const url2 = this.createUrl(path9, params3);
2825
- this.dispatchEvent(new ServiceEvent2("beforeRequest", { url: url2, params: params3, options, method: "PATCH" }));
2826
- const json3 = isObject2(data) || Array.isArray(data);
2827
- return fetch(url2, {
2828
- method: "PATCH",
2829
- body: json3 ? JSON.stringify(data) : data,
2830
- headers: await this.createHeaders({
2831
- ...options?.headers ?? {},
2832
- "Content-Type": `${json3 ? "application/json" : "text/plain"}; charset=UTF-8`
2833
- }),
2834
- credentials: this.credentials
2835
- });
2836
- }
2837
- async patch(path9, data, params3, options) {
2838
- const response = await this.patchRaw(path9, data, params3, options);
2839
- return this.handleResponse(response);
2840
- }
2841
- };
2842
- exports2.Service = Service2;
2843
- }
2844
- });
2845
-
2846
- // ../rp-client/dist/index.js
2847
- var require_dist = __commonJS({
2848
- "../rp-client/dist/index.js"(exports2) {
2849
- "use strict";
2850
- var __createBinding = exports2 && exports2.__createBinding || (Object.create ? function(o, m, k, k2) {
2851
- if (k2 === void 0) k2 = k;
2852
- var desc = Object.getOwnPropertyDescriptor(m, k);
2853
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
2854
- desc = { enumerable: true, get: function() {
2855
- return m[k];
2856
- } };
2857
- }
2858
- Object.defineProperty(o, k2, desc);
2859
- } : function(o, m, k, k2) {
2860
- if (k2 === void 0) k2 = k;
2861
- o[k2] = m[k];
2862
- });
2863
- var __exportStar = exports2 && exports2.__exportStar || function(m, exports3) {
2864
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
2865
- };
2866
- Object.defineProperty(exports2, "__esModule", { value: true });
2867
- __exportStar(require_types(), exports2);
2868
- __exportStar(require_cache(), exports2);
2869
- __exportStar(require_accountingProvider(), exports2);
2870
- __exportStar(require_service(), exports2);
2871
- }
2872
- });
2873
-
2874
2546
  // ../../.yarn/cache/ajv-npm-8.18.0-6e5e062765-e7517c4261.zip/node_modules/ajv/dist/compile/codegen/code.js
2875
2547
  var require_code = __commonJS({
2876
2548
  "../../.yarn/cache/ajv-npm-8.18.0-6e5e062765-e7517c4261.zip/node_modules/ajv/dist/compile/codegen/code.js"(exports2) {
@@ -4776,9 +4448,9 @@ var require_keyword = __commonJS({
4776
4448
  if (Array.isArray(def2.keyword) ? !def2.keyword.includes(keyword) : def2.keyword !== keyword) {
4777
4449
  throw new Error("ajv implementation error");
4778
4450
  }
4779
- const deps = def2.dependencies;
4780
- if (deps === null || deps === void 0 ? void 0 : deps.some((kwd) => !Object.prototype.hasOwnProperty.call(schema, kwd))) {
4781
- throw new Error(`parent schema must have dependencies of ${keyword}: ${deps.join(",")}`);
4451
+ const deps2 = def2.dependencies;
4452
+ if (deps2 === null || deps2 === void 0 ? void 0 : deps2.some((kwd) => !Object.prototype.hasOwnProperty.call(schema, kwd))) {
4453
+ throw new Error(`parent schema must have dependencies of ${keyword}: ${deps2.join(",")}`);
4782
4454
  }
4783
4455
  if (def2.validateSchema) {
4784
4456
  const valid = def2.validateSchema(schema[keyword]);
@@ -5822,7 +5494,7 @@ var require_compile = __commonJS({
5822
5494
  const schOrFunc = root.refs[ref];
5823
5495
  if (schOrFunc)
5824
5496
  return schOrFunc;
5825
- let _sch = resolve7.call(this, root, ref);
5497
+ let _sch = resolve8.call(this, root, ref);
5826
5498
  if (_sch === void 0) {
5827
5499
  const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
5828
5500
  const { schemaId } = this.opts;
@@ -5849,7 +5521,7 @@ var require_compile = __commonJS({
5849
5521
  function sameSchemaEnv(s1, s2) {
5850
5522
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
5851
5523
  }
5852
- function resolve7(root, ref) {
5524
+ function resolve8(root, ref) {
5853
5525
  let sch;
5854
5526
  while (typeof (sch = this.refs[ref]) == "string")
5855
5527
  ref = sch;
@@ -6424,7 +6096,7 @@ var require_fast_uri = __commonJS({
6424
6096
  }
6425
6097
  return uri;
6426
6098
  }
6427
- function resolve7(baseURI, relativeURI, options) {
6099
+ function resolve8(baseURI, relativeURI, options) {
6428
6100
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
6429
6101
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
6430
6102
  schemelessOptions.skipEscape = true;
@@ -6651,7 +6323,7 @@ var require_fast_uri = __commonJS({
6651
6323
  var fastUri = {
6652
6324
  SCHEMES,
6653
6325
  normalize,
6654
- resolve: resolve7,
6326
+ resolve: resolve8,
6655
6327
  resolveComponent,
6656
6328
  equal,
6657
6329
  serialize,
@@ -8198,14 +7870,14 @@ var require_dependencies = __commonJS({
8198
7870
  var util_1 = require_util();
8199
7871
  var code_1 = require_code2();
8200
7872
  exports2.error = {
8201
- message: ({ params: { property, depsCount, deps } }) => {
7873
+ message: ({ params: { property, depsCount, deps: deps2 } }) => {
8202
7874
  const property_ies = depsCount === 1 ? "property" : "properties";
8203
- return (0, codegen_1.str)`must have ${property_ies} ${deps} when property ${property} is present`;
7875
+ return (0, codegen_1.str)`must have ${property_ies} ${deps2} when property ${property} is present`;
8204
7876
  },
8205
- params: ({ params: { property, depsCount, deps, missingProperty } }) => (0, codegen_1._)`{property: ${property},
7877
+ params: ({ params: { property, depsCount, deps: deps2, missingProperty } }) => (0, codegen_1._)`{property: ${property},
8206
7878
  missingProperty: ${missingProperty},
8207
7879
  depsCount: ${depsCount},
8208
- deps: ${deps}}`
7880
+ deps: ${deps2}}`
8209
7881
  // TODO change to reference
8210
7882
  };
8211
7883
  var def2 = {
@@ -8225,8 +7897,8 @@ var require_dependencies = __commonJS({
8225
7897
  for (const key in schema) {
8226
7898
  if (key === "__proto__")
8227
7899
  continue;
8228
- const deps = Array.isArray(schema[key]) ? propertyDeps : schemaDeps;
8229
- deps[key] = schema[key];
7900
+ const deps2 = Array.isArray(schema[key]) ? propertyDeps : schemaDeps;
7901
+ deps2[key] = schema[key];
8230
7902
  }
8231
7903
  return [propertyDeps, schemaDeps];
8232
7904
  }
@@ -8236,23 +7908,23 @@ var require_dependencies = __commonJS({
8236
7908
  return;
8237
7909
  const missing = gen.let("missing");
8238
7910
  for (const prop in propertyDeps) {
8239
- const deps = propertyDeps[prop];
8240
- if (deps.length === 0)
7911
+ const deps2 = propertyDeps[prop];
7912
+ if (deps2.length === 0)
8241
7913
  continue;
8242
7914
  const hasProperty = (0, code_1.propertyInData)(gen, data, prop, it.opts.ownProperties);
8243
7915
  cxt.setParams({
8244
7916
  property: prop,
8245
- depsCount: deps.length,
8246
- deps: deps.join(", ")
7917
+ depsCount: deps2.length,
7918
+ deps: deps2.join(", ")
8247
7919
  });
8248
7920
  if (it.allErrors) {
8249
7921
  gen.if(hasProperty, () => {
8250
- for (const depProp of deps) {
7922
+ for (const depProp of deps2) {
8251
7923
  (0, code_1.checkReportMissingProp)(cxt, depProp);
8252
7924
  }
8253
7925
  });
8254
7926
  } else {
8255
- gen.if((0, codegen_1._)`${hasProperty} && (${(0, code_1.checkMissingProp)(cxt, deps, missing)})`);
7927
+ gen.if((0, codegen_1._)`${hasProperty} && (${(0, code_1.checkMissingProp)(cxt, deps2, missing)})`);
8256
7928
  (0, code_1.reportMissingProp)(cxt, missing);
8257
7929
  gen.else();
8258
7930
  }
@@ -8978,7 +8650,7 @@ var require_draft7 = __commonJS({
8978
8650
  });
8979
8651
 
8980
8652
  // ../../.yarn/cache/ajv-npm-8.18.0-6e5e062765-e7517c4261.zip/node_modules/ajv/dist/vocabularies/discriminator/types.js
8981
- var require_types2 = __commonJS({
8653
+ var require_types = __commonJS({
8982
8654
  "../../.yarn/cache/ajv-npm-8.18.0-6e5e062765-e7517c4261.zip/node_modules/ajv/dist/vocabularies/discriminator/types.js"(exports2) {
8983
8655
  "use strict";
8984
8656
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -8997,7 +8669,7 @@ var require_discriminator = __commonJS({
8997
8669
  "use strict";
8998
8670
  Object.defineProperty(exports2, "__esModule", { value: true });
8999
8671
  var codegen_1 = require_codegen();
9000
- var types_1 = require_types2();
8672
+ var types_1 = require_types();
9001
8673
  var compile_1 = require_compile();
9002
8674
  var ref_error_1 = require_ref_error();
9003
8675
  var util_1 = require_util();
@@ -9599,7 +9271,7 @@ var require_limit = __commonJS({
9599
9271
  });
9600
9272
 
9601
9273
  // ../../.yarn/__virtual__/ajv-formats-virtual-7723216a4c/0/cache/ajv-formats-npm-3.0.1-2662cf5b12-168d6bca1e.zip/node_modules/ajv-formats/dist/index.js
9602
- var require_dist2 = __commonJS({
9274
+ var require_dist = __commonJS({
9603
9275
  "../../.yarn/__virtual__/ajv-formats-virtual-7723216a4c/0/cache/ajv-formats-npm-3.0.1-2662cf5b12-168d6bca1e.zip/node_modules/ajv-formats/dist/index.js"(exports2, module2) {
9604
9276
  "use strict";
9605
9277
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -9649,7 +9321,7 @@ var fs2 = __toESM(require("node:fs"));
9649
9321
  var path = __toESM(require("node:path"));
9650
9322
  var os2 = __toESM(require("node:os"));
9651
9323
 
9652
- // ../rp-auth-client-v2/dist/client.js
9324
+ // ../rp-auth-client-v2/src/client.ts
9653
9325
  var DEFAULT_OPTIONS = {
9654
9326
  serviceUrl: null,
9655
9327
  redirectUrl: typeof window !== "undefined" ? window.location.href : null,
@@ -9663,8 +9335,7 @@ var ReportingAuthClientException = class extends Error {
9663
9335
  }
9664
9336
  };
9665
9337
  function accountingServiceUrl(serviceUrl, accounting2) {
9666
- if (accounting2 == null)
9667
- return serviceUrl;
9338
+ if (accounting2 == null) return serviceUrl;
9668
9339
  const match = /^(.*)\/ms\/auth\/?$/.exec(serviceUrl);
9669
9340
  if (!match) {
9670
9341
  throw new ReportingAuthClientException({
@@ -9674,12 +9345,26 @@ function accountingServiceUrl(serviceUrl, accounting2) {
9674
9345
  return `${match[1]}/ms/${accounting2}/auth`;
9675
9346
  }
9676
9347
  var ReportingAuthClient = class {
9348
+ constructor(options) {
9349
+ this.options = DEFAULT_OPTIONS;
9350
+ /**
9351
+ * Body of the last response, already parsed. A fetch body may be read only once, so every
9352
+ * method that consumes it parks the result here for readCredentials() to hand out — reading
9353
+ * it a second time throws "Body is unusable", which used to cost rp-cli the tokens it had
9354
+ * just been issued.
9355
+ *
9356
+ * Boxed because the parsed value may legitimately be null, and a body that never held
9357
+ * credentials (token info, an error) parks { value: null } so it is not read again either.
9358
+ */
9359
+ this.parsedResponse = null;
9360
+ this._expiresAt = null;
9361
+ this.setOptions(options);
9362
+ }
9677
9363
  get expiresAt() {
9678
9364
  return this._expiresAt;
9679
9365
  }
9680
9366
  get expiresIn() {
9681
- if (this._expiresAt === null)
9682
- return null;
9367
+ if (this._expiresAt === null) return null;
9683
9368
  return this._expiresAt - Math.floor(Date.now() / 1e3);
9684
9369
  }
9685
9370
  async getAuthInit() {
@@ -9700,17 +9385,28 @@ var ReportingAuthClient = class {
9700
9385
  }
9701
9386
  });
9702
9387
  }
9388
+ /** Keeps the parked body in step with the response it was read from */
9389
+ setResponse(response) {
9390
+ this.response = response;
9391
+ this.parsedResponse = null;
9392
+ return response;
9393
+ }
9394
+ /**
9395
+ * Reads the error body of the last response. It carries no credentials and the body is spent
9396
+ * afterwards, so it is parked as such. The HTTP status is attached because callers need to
9397
+ * tell a rejected token (401) from a service that is merely unwell (5xx).
9398
+ */
9399
+ async readErrorBody() {
9400
+ const body = await this.response.json();
9401
+ this.parsedResponse = { value: null };
9402
+ return { status: this.response.status, ...body };
9403
+ }
9703
9404
  setOptions(options) {
9704
9405
  Object.assign(this.options, options);
9705
9406
  if (!/^(https?|\/)/.test(this.options.serviceUrl)) {
9706
9407
  this.options.serviceUrl = `https://${this.options.serviceUrl}`;
9707
9408
  }
9708
9409
  }
9709
- constructor(options) {
9710
- this.options = DEFAULT_OPTIONS;
9711
- this._expiresAt = null;
9712
- this.setOptions(options);
9713
- }
9714
9410
  /**
9715
9411
  * Issues a token for another user. Like impersonate(), the accounting belongs in the
9716
9412
  * URL: delegateToken checks the caller's Admin right against the accounting of the
@@ -9721,7 +9417,7 @@ var ReportingAuthClient = class {
9721
9417
  const scope = options.scope ? Array.isArray(options.scope) ? options.scope.join(" ") : options.scope : "";
9722
9418
  const accounting2 = options.accounting ?? this.options.accounting;
9723
9419
  const serviceUrl = accountingServiceUrl(this.options.serviceUrl, accounting2);
9724
- this.response = await this.fetchWithAuth(`${serviceUrl}/delegateToken`, {
9420
+ this.setResponse(await this.fetchWithAuth(`${serviceUrl}/delegateToken`, {
9725
9421
  method: "POST",
9726
9422
  headers: {
9727
9423
  "Content-Type": "application/json"
@@ -9734,8 +9430,10 @@ var ReportingAuthClient = class {
9734
9430
  "scope": scope,
9735
9431
  "accounting": accounting2
9736
9432
  }))
9737
- });
9738
- return await this.response.json();
9433
+ }));
9434
+ const credentials = await this.response.json();
9435
+ this.parsedResponse = { value: credentials };
9436
+ return credentials;
9739
9437
  }
9740
9438
  /**
9741
9439
  * Impersonates the given user. The accounting is required in practice: rights are
@@ -9778,7 +9476,7 @@ var ReportingAuthClient = class {
9778
9476
  }
9779
9477
  }
9780
9478
  async authorize(authorizationCode) {
9781
- this.response = await this.fetchWithAuth(`${this.options.serviceUrl}/token`, {
9479
+ this.setResponse(await this.fetchWithAuth(`${this.options.serviceUrl}/token`, {
9782
9480
  method: "POST",
9783
9481
  headers: {
9784
9482
  "Content-Type": "application/json"
@@ -9789,10 +9487,9 @@ var ReportingAuthClient = class {
9789
9487
  "grant_type": "code",
9790
9488
  "code": authorizationCode
9791
9489
  }))
9792
- });
9490
+ }));
9793
9491
  if (!this.response.ok) {
9794
- const err = await this.response.json();
9795
- throw new ReportingAuthClientException(err);
9492
+ throw new ReportingAuthClientException(await this.readErrorBody());
9796
9493
  }
9797
9494
  return this.response;
9798
9495
  }
@@ -9816,17 +9513,16 @@ var ReportingAuthClient = class {
9816
9513
  window.location.href = url2;
9817
9514
  }
9818
9515
  async getTokenInfo() {
9819
- this.response = await this.fetchWithAuth(`${this.options.serviceUrl}/info`, {
9516
+ this.setResponse(await this.fetchWithAuth(`${this.options.serviceUrl}/info`, {
9820
9517
  method: "GET",
9821
9518
  headers: {
9822
9519
  "Content-Type": "application/json"
9823
9520
  }
9824
- });
9521
+ }));
9825
9522
  this._expiresAt = null;
9826
9523
  if (!this.response.ok) {
9827
9524
  if (this.response.headers.get("content-type")?.includes("application/json")) {
9828
- const err = await this.response.json();
9829
- throw new ReportingAuthClientException(err);
9525
+ throw new ReportingAuthClientException(await this.readErrorBody());
9830
9526
  } else {
9831
9527
  throw new ReportingAuthClientException({
9832
9528
  message: "Gateway communication error"
@@ -9834,6 +9530,7 @@ var ReportingAuthClient = class {
9834
9530
  }
9835
9531
  }
9836
9532
  const tokenInfo = await this.response.json();
9533
+ this.parsedResponse = { value: null };
9837
9534
  if (tokenInfo.expires_in != null) {
9838
9535
  this._expiresAt = Math.floor(Date.now() / 1e3) + tokenInfo.expires_in;
9839
9536
  }
@@ -9848,10 +9545,8 @@ var ReportingAuthClient = class {
9848
9545
  });
9849
9546
  }
9850
9547
  redirectToLogin() {
9851
- if (typeof window === "undefined")
9852
- throw new Error("Not in browser environment");
9853
- if (!this.options.loginUrl)
9854
- throw new Error("Login URL not set");
9548
+ if (typeof window === "undefined") throw new Error("Not in browser environment");
9549
+ if (!this.options.loginUrl) throw new Error("Login URL not set");
9855
9550
  let url2 = this.options.loginUrl;
9856
9551
  const queryParams = [];
9857
9552
  if (this.options.clientId) {
@@ -9866,9 +9561,11 @@ var ReportingAuthClient = class {
9866
9561
  window.location.href = url2;
9867
9562
  }
9868
9563
  async readCredentials() {
9869
- if (!this.response)
9870
- return null;
9871
- return await this.response.json();
9564
+ if (!this.response) return null;
9565
+ if (!this.parsedResponse) {
9566
+ this.parsedResponse = { value: await this.response.json() };
9567
+ }
9568
+ return this.parsedResponse.value;
9872
9569
  }
9873
9570
  async refreshToken() {
9874
9571
  const body = { grant_type: "refresh_token" };
@@ -9878,26 +9575,24 @@ var ReportingAuthClient = class {
9878
9575
  body.refresh_token = token.refresh_token;
9879
9576
  }
9880
9577
  }
9881
- if (this.options.clientId)
9882
- body.client_id = this.options.clientId;
9883
- if (this.options.clientSecret)
9884
- body.client_secret = this.options.clientSecret;
9885
- this.response = await fetch(`${this.options.serviceUrl}/token`, {
9578
+ if (this.options.clientId) body.client_id = this.options.clientId;
9579
+ if (this.options.clientSecret) body.client_secret = this.options.clientSecret;
9580
+ this.setResponse(await fetch(`${this.options.serviceUrl}/token`, {
9886
9581
  method: "POST",
9887
9582
  headers: {
9888
9583
  "Content-Type": "application/json"
9889
9584
  },
9890
9585
  credentials: body.refresh_token ? "omit" : "include",
9891
9586
  body: JSON.stringify(body)
9892
- });
9587
+ }));
9893
9588
  if (this.response.ok) {
9894
9589
  const result = await this.response.json();
9590
+ this.parsedResponse = { value: result };
9895
9591
  if (result?.expires_in) {
9896
9592
  this._expiresAt = Math.floor(Date.now() / 1e3) + result.expires_in;
9897
9593
  }
9898
9594
  } else {
9899
- const err = await this.response.json();
9900
- throw new ReportingAuthClientException(err);
9595
+ throw new ReportingAuthClientException(await this.readErrorBody());
9901
9596
  }
9902
9597
  return this.response;
9903
9598
  }
@@ -9908,8 +9603,260 @@ var ReportingAuthClient = class {
9908
9603
  }
9909
9604
  };
9910
9605
 
9911
- // src/config.ts
9912
- var import_rp_client = __toESM(require_dist());
9606
+ // ../rp-client/src/cache.ts
9607
+ var ServiceCache = class {
9608
+ constructor() {
9609
+ this.cache = /* @__PURE__ */ new Map();
9610
+ }
9611
+ createKey(params3) {
9612
+ let key = params3.path;
9613
+ if (params3.params) {
9614
+ const paramStrings = [];
9615
+ for (const k of Object.keys(params3.params).sort()) {
9616
+ paramStrings.push(`${k}=${params3.params[k]}`);
9617
+ }
9618
+ if (paramStrings.length > 0) {
9619
+ key += `?${paramStrings.join("&")}`;
9620
+ }
9621
+ }
9622
+ return key;
9623
+ }
9624
+ set(params3, data) {
9625
+ const key = this.createKey(params3);
9626
+ this.cache.set(key, data);
9627
+ }
9628
+ get(params3) {
9629
+ const key = this.createKey(params3);
9630
+ return this.cache.get(key);
9631
+ }
9632
+ has(params3) {
9633
+ const key = this.createKey(params3);
9634
+ return this.cache.has(key);
9635
+ }
9636
+ clear() {
9637
+ this.cache.clear();
9638
+ }
9639
+ };
9640
+
9641
+ // ../rp-client/src/accountingProvider.ts
9642
+ var AccountingProvider = class {
9643
+ constructor() {
9644
+ this._a = null;
9645
+ this._accounting = null;
9646
+ }
9647
+ get a() {
9648
+ return this._a;
9649
+ }
9650
+ set a(a) {
9651
+ this._a = a;
9652
+ if (this.accounting?.accounting != a) {
9653
+ this._accounting = null;
9654
+ }
9655
+ }
9656
+ get accounting() {
9657
+ return this._accounting;
9658
+ }
9659
+ set accounting(accounting2) {
9660
+ if (accounting2) {
9661
+ this._a = accounting2.accounting ?? null;
9662
+ }
9663
+ this._accounting = accounting2;
9664
+ }
9665
+ };
9666
+
9667
+ // ../rp-client/src/service.ts
9668
+ var import_qs = __toESM(require_lib());
9669
+ function isObject(value) {
9670
+ return value !== null && typeof value === "object" && !Array.isArray(value);
9671
+ }
9672
+ function serializeRequestParams(params3) {
9673
+ const p = {};
9674
+ for (const k in params3) {
9675
+ switch (k) {
9676
+ case "customParams":
9677
+ p["p"] = JSON.stringify(params3.customParams);
9678
+ break;
9679
+ default:
9680
+ p[k] = isObject(params3[k]) || Array.isArray(params3[k]) ? JSON.stringify(params3[k]) : params3[k];
9681
+ break;
9682
+ }
9683
+ }
9684
+ return import_qs.default.stringify(p, { addQueryPrefix: false });
9685
+ }
9686
+ var ServiceException = class extends Error {
9687
+ constructor(args) {
9688
+ super(args?.data?.message || "Unknown error");
9689
+ Object.assign(this, args);
9690
+ }
9691
+ };
9692
+ var ServiceEvent = class extends Event {
9693
+ constructor(name, args) {
9694
+ super(name);
9695
+ Object.assign(this, args);
9696
+ }
9697
+ };
9698
+ var Service = class extends EventTarget {
9699
+ constructor(options) {
9700
+ super();
9701
+ this.accountingProvider = null;
9702
+ this.tokenProvider = null;
9703
+ this._cache = null;
9704
+ this.options = { ...options };
9705
+ if (!/^(https?|\/)/.test(this.options.baseUrl)) {
9706
+ this.options.baseUrl = `https://${this.options.baseUrl}`;
9707
+ }
9708
+ }
9709
+ get a() {
9710
+ return this.accountingProvider?.a;
9711
+ }
9712
+ get name() {
9713
+ return this.options.name;
9714
+ }
9715
+ get cache() {
9716
+ if (!this._cache) this._cache = new ServiceCache();
9717
+ return this._cache;
9718
+ }
9719
+ createUrl(path9 = "", params3) {
9720
+ if (this.options.xdebugSession) {
9721
+ params3 = params3 || {};
9722
+ params3.XDEBUG_SESSION = this.options.xdebugSession === true ? "1" : this.options.xdebugSession.toString();
9723
+ }
9724
+ if (this.options.params && Object.keys(this.options.params).length > 0) {
9725
+ params3 = { ...this.options.params, ...params3 };
9726
+ }
9727
+ let queryString = params3 ? serializeRequestParams(params3) : "";
9728
+ if (queryString) queryString = `?${queryString}`;
9729
+ const a = params3 && params3.accounting ? params3.accounting.toString() : this.a ? this.a.toString() : null;
9730
+ path9 = path9.replace(/^\//, "");
9731
+ const parts = [this.options.baseUrl, a, this.options.name, path9];
9732
+ return parts.filter((p) => p != null && p !== "").join("/") + queryString;
9733
+ }
9734
+ async createHeaders(headers) {
9735
+ const defaultHeaders = {};
9736
+ if (this.tokenProvider) {
9737
+ const token = await this.tokenProvider();
9738
+ if (token) {
9739
+ defaultHeaders["Authorization"] = `Bearer ${token}`;
9740
+ }
9741
+ }
9742
+ return { ...defaultHeaders, ...headers };
9743
+ }
9744
+ get credentials() {
9745
+ return this.tokenProvider ? void 0 : "include";
9746
+ }
9747
+ async handleError(response) {
9748
+ const contentType = response.headers.get("Content-Type");
9749
+ let error48;
9750
+ if (contentType?.includes("application/json")) {
9751
+ error48 = await response.json();
9752
+ error48.status = response.status;
9753
+ } else {
9754
+ error48 = { status: response.status, message: "Unknown error", error: "unknown" };
9755
+ }
9756
+ if (this.options.throwExceptions) {
9757
+ throw new ServiceException({ data: error48, url: response.url });
9758
+ }
9759
+ }
9760
+ async handleResponse(response, parse3) {
9761
+ if (!response.ok) {
9762
+ await this.handleError(response);
9763
+ return null;
9764
+ }
9765
+ const contentType = response.headers.get("Content-Type");
9766
+ if (contentType?.includes("application/json") && parse3 !== false) {
9767
+ return await response.json();
9768
+ } else {
9769
+ return await response.text();
9770
+ }
9771
+ }
9772
+ async getRaw(path9, params3, options) {
9773
+ const url2 = this.createUrl(path9, params3);
9774
+ this.dispatchEvent(new ServiceEvent("beforeRequest", { url: url2, params: params3, options, method: "GET" }));
9775
+ return fetch(url2, {
9776
+ headers: await this.createHeaders(options?.headers),
9777
+ credentials: this.credentials
9778
+ });
9779
+ }
9780
+ async get(path9, params3, options) {
9781
+ if (options?.cache) {
9782
+ const cachedData = this.cache.get({ path: path9, params: params3 });
9783
+ if (cachedData) return cachedData;
9784
+ }
9785
+ const response = await this.getRaw(path9, params3, options);
9786
+ const responseData = await this.handleResponse(response, options?.parseResponse);
9787
+ if (options?.cache && responseData) {
9788
+ this.cache.set({ path: path9, params: params3 }, responseData);
9789
+ }
9790
+ return responseData;
9791
+ }
9792
+ async postRaw(path9, data, params3, options) {
9793
+ const url2 = this.createUrl(path9, params3);
9794
+ this.dispatchEvent(new ServiceEvent("beforeRequest", { url: url2, params: params3, options, method: "POST" }));
9795
+ const json3 = isObject(data) || Array.isArray(data);
9796
+ return fetch(url2, {
9797
+ method: "POST",
9798
+ body: json3 ? JSON.stringify(data) : data,
9799
+ headers: await this.createHeaders({
9800
+ ...options?.headers ?? {},
9801
+ "Content-Type": `${json3 ? "application/json" : "text/plain"}; charset=UTF-8`
9802
+ }),
9803
+ credentials: this.credentials
9804
+ });
9805
+ }
9806
+ async post(path9, data, params3, options) {
9807
+ const response = await this.postRaw(path9, data, params3, options);
9808
+ return this.handleResponse(response);
9809
+ }
9810
+ async deleteRaw(path9, params3, options) {
9811
+ const url2 = this.createUrl(path9, params3);
9812
+ this.dispatchEvent(new ServiceEvent("beforeRequest", { url: url2, params: params3, options, method: "DELETE" }));
9813
+ return fetch(url2, {
9814
+ method: "DELETE",
9815
+ headers: await this.createHeaders(options?.headers),
9816
+ credentials: this.credentials
9817
+ });
9818
+ }
9819
+ async delete(path9, params3, options) {
9820
+ const response = await this.deleteRaw(path9, params3, options);
9821
+ return this.handleResponse(response);
9822
+ }
9823
+ async putRaw(path9, data, params3, options) {
9824
+ const url2 = this.createUrl(path9, params3);
9825
+ this.dispatchEvent(new ServiceEvent("beforeRequest", { url: url2, params: params3, options, method: "PUT" }));
9826
+ const json3 = isObject(data) || Array.isArray(data);
9827
+ return fetch(url2, {
9828
+ method: "PUT",
9829
+ body: json3 ? JSON.stringify(data) : data,
9830
+ headers: await this.createHeaders({
9831
+ ...options?.headers ?? {},
9832
+ "Content-Type": `${json3 ? "application/json" : "text/plain"}; charset=UTF-8`
9833
+ }),
9834
+ credentials: this.credentials
9835
+ });
9836
+ }
9837
+ async put(path9, data, params3, options) {
9838
+ const response = await this.putRaw(path9, data, params3, options);
9839
+ return this.handleResponse(response);
9840
+ }
9841
+ async patchRaw(path9, data, params3, options) {
9842
+ const url2 = this.createUrl(path9, params3);
9843
+ this.dispatchEvent(new ServiceEvent("beforeRequest", { url: url2, params: params3, options, method: "PATCH" }));
9844
+ const json3 = isObject(data) || Array.isArray(data);
9845
+ return fetch(url2, {
9846
+ method: "PATCH",
9847
+ body: json3 ? JSON.stringify(data) : data,
9848
+ headers: await this.createHeaders({
9849
+ ...options?.headers ?? {},
9850
+ "Content-Type": `${json3 ? "application/json" : "text/plain"}; charset=UTF-8`
9851
+ }),
9852
+ credentials: this.credentials
9853
+ });
9854
+ }
9855
+ async patch(path9, data, params3, options) {
9856
+ const response = await this.patchRaw(path9, data, params3, options);
9857
+ return this.handleResponse(response);
9858
+ }
9859
+ };
9913
9860
 
9914
9861
  // src/crypto.ts
9915
9862
  var crypto = __toESM(require("node:crypto"));
@@ -9971,6 +9918,44 @@ var CONFIG_DIR = path.join(os2.homedir(), ".config", "reporting-bi");
9971
9918
  var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
9972
9919
  var CLIENT_ID = "0D6693D4-4358-4C08-BA80-A94A97B7F47C";
9973
9920
  var CLIENT_SECRET = "48481427819bb41798bd74fa109c90b184ca4d64d20e60dfbe8430299f06f73e";
9921
+ var EXIT_LOGIN_REQUIRED = 2;
9922
+ var LOGIN_REQUIRED_MARKER = "rpbi: login_required";
9923
+ var SPID_MARKER = "rpbi: spid";
9924
+ var LoginRequiredError = class extends Error {
9925
+ constructor(domain2, detail) {
9926
+ super(`Session expired. Run "rpbi login ${domain2}" again.`);
9927
+ this.domain = domain2;
9928
+ this.detail = detail;
9929
+ }
9930
+ };
9931
+ var mcpMode = false;
9932
+ function setMcpMode() {
9933
+ mcpMode = true;
9934
+ }
9935
+ function tokenExpiration(token) {
9936
+ if (!token) return void 0;
9937
+ try {
9938
+ const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf-8"));
9939
+ return typeof payload.exp === "number" ? payload.exp : void 0;
9940
+ } catch {
9941
+ return void 0;
9942
+ }
9943
+ }
9944
+ function sessionExpiration(tokens) {
9945
+ return tokens.refresh_expires_at ?? tokenExpiration(tokens.refresh_token);
9946
+ }
9947
+ function accessExpiration(tokens) {
9948
+ return tokens.expires_at || tokenExpiration(tokens.access_token);
9949
+ }
9950
+ function sessionState(tokens, now = Math.floor(Date.now() / 1e3)) {
9951
+ const accessEnd = accessExpiration(tokens);
9952
+ const accessValid = accessEnd === void 0 || accessEnd > now;
9953
+ if (!tokens.refresh_token) return accessValid ? "unknown" : "expired";
9954
+ const sessionEnd = sessionExpiration(tokens);
9955
+ if (sessionEnd === void 0) return "unknown";
9956
+ if (sessionEnd <= now) return "expired";
9957
+ return accessValid ? "ok" : "stale";
9958
+ }
9974
9959
  function loadCredentials() {
9975
9960
  try {
9976
9961
  const raw = fs2.readFileSync(CREDENTIALS_FILE, "utf-8");
@@ -9999,21 +9984,29 @@ function findLocalConfig(startDir) {
9999
9984
  dir = parent;
10000
9985
  }
10001
9986
  }
10002
- function getActiveConnection(cwd) {
9987
+ function getActiveConnection(cwd, connection) {
10003
9988
  const creds = loadCredentials();
10004
9989
  const local = findLocalConfig(cwd);
10005
- const domain2 = local?.domain ?? creds.active;
9990
+ const domain2 = connection ?? local?.domain ?? creds.active;
10006
9991
  if (!domain2 || !creds.connections[domain2]) return null;
10007
9992
  const tokens = { ...creds.connections[domain2] };
10008
- if (local?.accounting) {
9993
+ if (local?.accounting && local.domain === domain2) {
10009
9994
  tokens.accounting = local.accounting;
10010
9995
  }
10011
9996
  return { domain: domain2, tokens };
10012
9997
  }
10013
- function storeTokens(domain2, tokens) {
9998
+ function storeTokens(domain2, tokens, activate = true) {
10014
9999
  const creds = loadCredentials();
10015
- creds.connections[domain2] = tokens;
10016
- creds.active = domain2;
10000
+ const previous = creds.connections[domain2];
10001
+ creds.connections[domain2] = {
10002
+ ...previous,
10003
+ ...tokens,
10004
+ // spelled out because both are optional: a refresh token with no readable expiry must
10005
+ // not inherit the previous one, while an unspecified accounting keeps the current pick
10006
+ refresh_expires_at: tokens.refresh_expires_at,
10007
+ accounting: tokens.accounting ?? previous?.accounting
10008
+ };
10009
+ if (activate) creds.active = domain2;
10017
10010
  saveCredentials(creds);
10018
10011
  }
10019
10012
  function removeTokens(domain2) {
@@ -10035,6 +10028,31 @@ function setActive(domain2, accounting2) {
10035
10028
  saveCredentials(creds);
10036
10029
  return true;
10037
10030
  }
10031
+ function statusReport(cwd, now = Math.floor(Date.now() / 1e3)) {
10032
+ const creds = loadCredentials();
10033
+ const local = findLocalConfig(cwd);
10034
+ const activeDomain = local?.domain ?? creds.active;
10035
+ return {
10036
+ active: creds.active,
10037
+ local,
10038
+ connections: Object.keys(creds.connections).map((domain2) => {
10039
+ const tokens = creds.connections[domain2];
10040
+ const accessEnd = accessExpiration(tokens);
10041
+ const sessionEnd = sessionExpiration(tokens);
10042
+ return {
10043
+ domain: domain2,
10044
+ active: domain2 === activeDomain,
10045
+ accounting: tokens.accounting ?? null,
10046
+ state: sessionState(tokens, now),
10047
+ token_expires_at: accessEnd ?? null,
10048
+ token_expires_in: accessEnd === void 0 ? null : accessEnd - now,
10049
+ session_expires_at: sessionEnd ?? null,
10050
+ session_expires_in: sessionEnd === void 0 ? null : sessionEnd - now,
10051
+ has_refresh_token: !!tokens.refresh_token
10052
+ };
10053
+ })
10054
+ };
10055
+ }
10038
10056
  function createAuthClient(domain2, tokens) {
10039
10057
  return new ReportingAuthClient({
10040
10058
  serviceUrl: `${domain2}/auth`,
@@ -10043,49 +10061,107 @@ function createAuthClient(domain2, tokens) {
10043
10061
  tokenProvider: tokens ? () => ({ access_token: tokens.access_token, refresh_token: tokens.refresh_token }) : void 0
10044
10062
  });
10045
10063
  }
10064
+ var DEAD_TOKEN_ERRORS = ["invalid_grant", "invalid_jwt", "invalid_token"];
10065
+ function isSessionRefused(err) {
10066
+ return err.data?.status === 401 || DEAD_TOKEN_ERRORS.includes(err.data?.error ?? "");
10067
+ }
10068
+ function refusalReason(err) {
10069
+ if (err instanceof ReportingAuthClientException) {
10070
+ return err.message || err.data?.error_description || err.data?.error || "refresh refused";
10071
+ }
10072
+ return err instanceof Error ? err.message : String(err);
10073
+ }
10074
+ async function awaitRotatedTokens(domain2, spentRefreshToken) {
10075
+ for (let attempt = 0; attempt < 6; attempt++) {
10076
+ const stored = loadCredentials().connections[domain2];
10077
+ if (stored?.refresh_token && stored.refresh_token !== spentRefreshToken) return stored;
10078
+ await new Promise((resolve8) => setTimeout(resolve8, 100));
10079
+ }
10080
+ return null;
10081
+ }
10082
+ async function refreshTokens(conn, now) {
10083
+ const client = createAuthClient(conn.domain, conn.tokens);
10084
+ await client.refreshToken();
10085
+ const credentials = await client.readCredentials();
10086
+ if (!credentials?.access_token) throw new LoginRequiredError(conn.domain, "no access token in the refresh response");
10087
+ conn.tokens.access_token = credentials.access_token;
10088
+ if (credentials.refresh_token) {
10089
+ conn.tokens.refresh_token = credentials.refresh_token;
10090
+ conn.tokens.refresh_expires_at = tokenExpiration(credentials.refresh_token);
10091
+ }
10092
+ conn.tokens.expires_at = credentials.expires_in ? now + credentials.expires_in : 0;
10093
+ storeTokens(conn.domain, conn.tokens, false);
10094
+ }
10046
10095
  async function ensureValidToken(conn) {
10047
10096
  const now = Math.floor(Date.now() / 1e3);
10048
10097
  const margin = 60;
10049
- if (conn.tokens.expires_at && conn.tokens.expires_at > now + margin) return;
10098
+ const accessEnd = accessExpiration(conn.tokens);
10099
+ if (accessEnd !== void 0 && accessEnd > now + margin) return;
10100
+ if (sessionState(conn.tokens, now) === "expired") throw new LoginRequiredError(conn.domain);
10050
10101
  if (!conn.tokens.refresh_token) return;
10051
- const client = createAuthClient(conn.domain, conn.tokens);
10052
- try {
10053
- await client.refreshToken();
10054
- const credentials = await client.readCredentials();
10055
- if (credentials?.access_token) {
10056
- conn.tokens.access_token = credentials.access_token;
10057
- if (credentials.refresh_token) conn.tokens.refresh_token = credentials.refresh_token;
10058
- conn.tokens.expires_at = credentials.expires_in ? now + credentials.expires_in : 0;
10059
- storeTokens(conn.domain, conn.tokens);
10102
+ for (let attempt = 0; attempt < 3; attempt++) {
10103
+ try {
10104
+ await refreshTokens(conn, now);
10105
+ return;
10106
+ } catch (err) {
10107
+ if (err instanceof LoginRequiredError) throw err;
10108
+ if (!(err instanceof ReportingAuthClientException) || !isSessionRefused(err)) {
10109
+ console.error("Token refresh failed:", refusalReason(err));
10110
+ return;
10111
+ }
10112
+ const stored = await awaitRotatedTokens(conn.domain, conn.tokens.refresh_token);
10113
+ if (!stored) throw new LoginRequiredError(conn.domain, refusalReason(err));
10114
+ conn.tokens.access_token = stored.access_token;
10115
+ conn.tokens.refresh_token = stored.refresh_token;
10116
+ conn.tokens.expires_at = stored.expires_at;
10117
+ conn.tokens.refresh_expires_at = stored.refresh_expires_at;
10118
+ const adoptedEnd = accessExpiration(conn.tokens);
10119
+ if (adoptedEnd !== void 0 && adoptedEnd > now + margin) return;
10060
10120
  }
10061
- } catch (err) {
10062
- console.error("Token refresh failed:", err instanceof Error ? err.message : err);
10063
- console.error(`Session expired. Run "rpbi login ${conn.domain}" again.`);
10064
10121
  }
10122
+ throw new LoginRequiredError(conn.domain, "the refresh token was refused repeatedly");
10123
+ }
10124
+ function abort(message) {
10125
+ if (mcpMode) throw new Error(message);
10126
+ console.error(message);
10127
+ process.exit(1);
10128
+ }
10129
+ function globalFlagValue(flag) {
10130
+ const index = process.argv.indexOf(`--${flag}`);
10131
+ if (index === -1) return void 0;
10132
+ const value = process.argv[index + 1];
10133
+ return value && !value.startsWith("-") ? value : void 0;
10065
10134
  }
10066
10135
  async function createService(name, options) {
10067
10136
  const cwd = typeof options === "object" ? options.cwd : void 0;
10068
- const conn = getActiveConnection(cwd);
10137
+ const connection = (typeof options === "object" ? options.connection : void 0) ?? globalFlagValue("connection");
10138
+ const conn = getActiveConnection(cwd, connection);
10069
10139
  if (!conn) {
10070
- console.error('Not logged in. Run "rpbi login" first.');
10071
- process.exit(1);
10140
+ abort(connection ? `Not logged in to "${connection}". Run "rpbi login ${connection}" first.` : 'Not logged in. Run "rpbi login" first.');
10072
10141
  }
10073
10142
  const accountingOverride = typeof options === "number" ? options : void 0;
10074
10143
  const accountingOptional = typeof options === "object" && options.accountingOptional;
10075
10144
  const accounting2 = accountingOverride ?? conn.tokens.accounting;
10076
- if (!accounting2 && !accountingOptional) {
10077
- console.error('No accounting set. Run "rpbi use <domain> --accounting <id>" first.');
10078
- process.exit(1);
10079
- }
10080
- await ensureValidToken(conn);
10081
- const service = new import_rp_client.Service({
10145
+ if (!accounting2 && !accountingOptional) abort('No accounting set. Run "rpbi use <domain> --accounting <id>" first.');
10146
+ try {
10147
+ await ensureValidToken(conn);
10148
+ } catch (err) {
10149
+ if (mcpMode || !(err instanceof LoginRequiredError)) throw err;
10150
+ const detail = err.detail ? `Token refresh failed: ${err.detail}
10151
+ ` : "";
10152
+ fs2.writeSync(2, `${detail}${LOGIN_REQUIRED_MARKER} ${conn.domain}
10153
+ ${err.message}
10154
+ `);
10155
+ process.exit(EXIT_LOGIN_REQUIRED);
10156
+ }
10157
+ const service = new Service({
10082
10158
  name,
10083
10159
  baseUrl: `${conn.domain}/ms`,
10084
10160
  throwExceptions: true
10085
10161
  });
10086
10162
  service.tokenProvider = () => conn.tokens.access_token;
10087
10163
  if (accounting2) {
10088
- const ap = new import_rp_client.AccountingProvider();
10164
+ const ap = new AccountingProvider();
10089
10165
  ap.a = accounting2;
10090
10166
  service.accountingProvider = ap;
10091
10167
  }
@@ -10309,6 +10385,8 @@ async function login(args) {
10309
10385
  access_token: credentials.access_token,
10310
10386
  refresh_token: credentials.refresh_token || "",
10311
10387
  expires_at: expiresAt,
10388
+ // only the JWT carries the session length, the token response does not
10389
+ refresh_expires_at: tokenExpiration(credentials.refresh_token),
10312
10390
  accounting: accounting2
10313
10391
  });
10314
10392
  console.log(`Logged in to ${domain2} successfully.`);
@@ -10324,12 +10402,12 @@ async function login(args) {
10324
10402
  }
10325
10403
  }
10326
10404
  function getAuthorizationCode(domain2) {
10327
- return new Promise((resolve7) => {
10405
+ return new Promise((resolve8) => {
10328
10406
  let timeout;
10329
10407
  const done = (result) => {
10330
10408
  clearTimeout(timeout);
10331
10409
  server.close();
10332
- resolve7(result);
10410
+ resolve8(result);
10333
10411
  };
10334
10412
  const server = http.createServer((req, res) => {
10335
10413
  const url2 = new URL(req.url || "", "http://localhost");
@@ -10400,41 +10478,47 @@ function printLogo() {
10400
10478
  }
10401
10479
 
10402
10480
  // src/commands/status.ts
10403
- function status() {
10481
+ function formatDuration(seconds) {
10482
+ const days = Math.floor(seconds / 86400);
10483
+ const hours = Math.floor(seconds % 86400 / 3600);
10484
+ const minutes = Math.floor(seconds % 3600 / 60);
10485
+ if (days > 0) return `${days}d ${hours}h`;
10486
+ if (hours > 0) return `${hours}h ${minutes}m`;
10487
+ return `${minutes}m`;
10488
+ }
10489
+ function tokenExpiry(expiresIn) {
10490
+ if (expiresIn === null) return "unknown";
10491
+ return expiresIn <= 0 ? "expired" : formatDuration(expiresIn);
10492
+ }
10493
+ function sessionExpiry(connection) {
10494
+ if (connection.state === "expired") return `expired \u2014 run "rpbi login ${connection.domain}"`;
10495
+ if (connection.session_expires_in === null) return "unknown";
10496
+ return `${formatDuration(connection.session_expires_in)} left`;
10497
+ }
10498
+ function status(args = []) {
10499
+ const { flags } = parseArgs(args);
10500
+ const report2 = statusReport();
10501
+ if (flags.json) {
10502
+ console.log(JSON.stringify(report2, null, 2));
10503
+ return;
10504
+ }
10404
10505
  printLogo();
10405
- const creds = loadCredentials();
10406
- const local = findLocalConfig();
10407
- const domains = Object.keys(creds.connections);
10408
- if (local) {
10506
+ if (report2.local) {
10409
10507
  console.log(`Local config (.rpbi/config.json):`);
10410
- console.log(` domain: ${local.domain}`);
10411
- console.log(` accounting: ${local.accounting ?? "not set"}`);
10508
+ console.log(` domain: ${report2.local.domain}`);
10509
+ console.log(` accounting: ${report2.local.accounting ?? "not set"}`);
10412
10510
  console.log();
10413
10511
  }
10414
- if (domains.length === 0) {
10512
+ if (report2.connections.length === 0) {
10415
10513
  console.log("Not logged in.");
10416
10514
  return;
10417
10515
  }
10418
- for (const domain2 of domains) {
10419
- const tokens = creds.connections[domain2];
10420
- const isActive = local ? local.domain === domain2 : creds.active === domain2;
10421
- const now = Math.floor(Date.now() / 1e3);
10422
- const expiresIn = tokens.expires_at - now;
10423
- let expiry;
10424
- if (tokens.expires_at === 0) {
10425
- expiry = "unknown";
10426
- } else if (expiresIn <= 0) {
10427
- expiry = "expired";
10428
- } else {
10429
- const minutes = Math.floor(expiresIn / 60);
10430
- const hours = Math.floor(minutes / 60);
10431
- expiry = hours > 0 ? `${hours}h ${minutes % 60}m` : `${minutes}m`;
10432
- }
10433
- const marker = isActive ? " (active)" : "";
10434
- console.log(`${domain2}${marker}`);
10435
- console.log(` accounting: ${tokens.accounting ?? "not set"}`);
10436
- console.log(` token expires: ${expiry}`);
10437
- console.log(` refresh token: ${tokens.refresh_token ? "yes" : "no"}`);
10516
+ for (const connection of report2.connections) {
10517
+ console.log(`${connection.domain}${connection.active ? " (active)" : ""}`);
10518
+ console.log(` accounting: ${connection.accounting ?? "not set"}`);
10519
+ console.log(` token expires: ${tokenExpiry(connection.token_expires_in)}`);
10520
+ console.log(` session: ${sessionExpiry(connection)}`);
10521
+ console.log(` refresh token: ${connection.has_refresh_token ? "yes" : "no"}`);
10438
10522
  }
10439
10523
  }
10440
10524
 
@@ -10599,6 +10683,43 @@ function walkDir(dir, root, knownPaths, result) {
10599
10683
  }
10600
10684
  }
10601
10685
  }
10686
+ function removeEmptyDirs(dir, root) {
10687
+ dir = path3.resolve(dir);
10688
+ root = path3.resolve(root);
10689
+ while (dir.startsWith(root) && dir !== root) {
10690
+ try {
10691
+ if (fs4.readdirSync(dir).length > 0) return;
10692
+ fs4.rmdirSync(dir);
10693
+ } catch {
10694
+ return;
10695
+ }
10696
+ dir = path3.dirname(dir);
10697
+ }
10698
+ }
10699
+ function removeVanishedFiles(root, previousIndex, serverPaths, computeCurrentHash) {
10700
+ for (const entry of previousIndex) {
10701
+ if (serverPaths.has(entry.path) || isInternalPath(entry.path)) continue;
10702
+ const absPath = path3.join(root, entry.path);
10703
+ if (!fs4.existsSync(absPath)) continue;
10704
+ try {
10705
+ if (computeCurrentHash(absPath, entry) !== entry.hash) {
10706
+ console.log(` Kept ${entry.path} (deleted on server, but modified locally)`);
10707
+ continue;
10708
+ }
10709
+ fs4.rmSync(absPath);
10710
+ removeEmptyDirs(path3.dirname(absPath), root);
10711
+ console.log(` Removed ${entry.path} (deleted on server)`);
10712
+ } catch (err) {
10713
+ console.error(` Failed to remove ${entry.path}: ${err instanceof Error ? err.message : err}`);
10714
+ }
10715
+ }
10716
+ }
10717
+ function entriesToDelete(root, index, relPath) {
10718
+ const prefix = `${relPath}/`;
10719
+ return index.filter(
10720
+ (e) => (e.path === relPath || e.path.startsWith(prefix)) && !fs4.existsSync(path3.join(root, e.path))
10721
+ );
10722
+ }
10602
10723
  function detectModifiedFiles(root, index, getPath, computeCurrentHash, getStoredHash) {
10603
10724
  const modified = [];
10604
10725
  for (const entry of index) {
@@ -10694,7 +10815,7 @@ function printRemoteStatus(label, root, config2, flags, result) {
10694
10815
  for (const key of Object.keys(result)) {
10695
10816
  result[key] = result[key].filter((f) => !isInternalPath(f));
10696
10817
  }
10697
- const { modifiedLocally, modifiedOnServer, conflict, new: newFiles, localOnly, serverOnly } = result;
10818
+ const { modifiedLocally, modifiedOnServer, conflict, new: newFiles, deletedLocally, localOnly, serverOnly } = result;
10698
10819
  if (flags.json === "true") {
10699
10820
  console.log(JSON.stringify(result));
10700
10821
  return;
@@ -10702,7 +10823,7 @@ function printRemoteStatus(label, root, config2, flags, result) {
10702
10823
  console.log(`${label} workspace: ${root}`);
10703
10824
  console.log(`Domain: ${config2?.domain} | Accounting: ${config2?.accounting}`);
10704
10825
  console.log("");
10705
- const total = modifiedLocally.length + modifiedOnServer.length + conflict.length + newFiles.length + localOnly.length + serverOnly.length;
10826
+ const total = modifiedLocally.length + modifiedOnServer.length + conflict.length + newFiles.length + deletedLocally.length + localOnly.length + serverOnly.length;
10706
10827
  if (total === 0) {
10707
10828
  console.log("In sync with server.");
10708
10829
  return;
@@ -10723,8 +10844,12 @@ function printRemoteStatus(label, root, config2, flags, result) {
10723
10844
  console.log("New (untracked):");
10724
10845
  for (const f of newFiles) console.log(` + ${f}`);
10725
10846
  }
10847
+ if (deletedLocally.length > 0) {
10848
+ console.log("Deleted locally (push deletes them on the server):");
10849
+ for (const f of deletedLocally) console.log(` D ${f}`);
10850
+ }
10726
10851
  if (localOnly.length > 0) {
10727
- console.log("Local only (deleted on server):");
10852
+ console.log("Deleted on server (pull removes them locally):");
10728
10853
  for (const f of localOnly) console.log(` - ${f}`);
10729
10854
  }
10730
10855
  if (serverOnly.length > 0) {
@@ -10770,7 +10895,9 @@ async function pull(flags) {
10770
10895
  const wikiService = await createService("wiki", { cwd: dir });
10771
10896
  const langs = flags.langs ? flags.langs.split(",") : SYNC_LANGS;
10772
10897
  console.log(`Pulling wiki to ${dir}...`);
10898
+ const previousIndex = readIndex(dir);
10773
10899
  const index = [];
10900
+ const serverPaths = /* @__PURE__ */ new Set();
10774
10901
  for (const lang of langs) {
10775
10902
  console.log(`Pulling pages for language: ${lang}`);
10776
10903
  const pages = await wikiService.get("pages", {
@@ -10783,6 +10910,7 @@ async function pull(flags) {
10783
10910
  if (!page.id_wiki_page || !page.name) continue;
10784
10911
  const relPath = pageToRelPath({ ...page, lang });
10785
10912
  const absPath = path4.join(dir, relPath);
10913
+ serverPaths.add(relPath);
10786
10914
  try {
10787
10915
  const content4 = await wikiService.get(
10788
10916
  `pages/${page.id_wiki_page}/content`,
@@ -10817,6 +10945,7 @@ async function pull(flags) {
10817
10945
  const lang = file2.lang || "cs";
10818
10946
  const relPath = fileToRelPath({ ...file2, lang });
10819
10947
  const absPath = path4.join(dir, relPath);
10948
+ serverPaths.add(relPath);
10820
10949
  try {
10821
10950
  const response = await wikiService.getRaw(`download/${file2.id_wiki_file}`);
10822
10951
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
@@ -10836,6 +10965,18 @@ async function pull(flags) {
10836
10965
  }
10837
10966
  }
10838
10967
  console.log(` Pulled ${pulledFiles} files`);
10968
+ for (const entry of previousIndex) {
10969
+ if (entry.type === "page" && !langs.includes(entry.lang)) {
10970
+ index.push(entry);
10971
+ serverPaths.add(entry.path);
10972
+ }
10973
+ }
10974
+ removeVanishedFiles(
10975
+ dir,
10976
+ previousIndex,
10977
+ serverPaths,
10978
+ (absPath, entry) => entry.type === "page" ? computePageHash(absPath) : computeFileHash(absPath)
10979
+ );
10839
10980
  writeConfig(dir, {
10840
10981
  domain: conn.domain,
10841
10982
  accounting: conn.tokens.accounting,
@@ -10874,8 +11015,12 @@ async function push(flags, files) {
10874
11015
  for (const relPath of filesToPush) {
10875
11016
  const absPath = path4.join(root, relPath);
10876
11017
  if (!fs5.existsSync(absPath)) {
10877
- const entry2 = indexMap.get(relPath);
10878
- if (entry2) {
11018
+ const entries = entriesToDelete(root, index, relPath);
11019
+ if (entries.length === 0) {
11020
+ console.log(` Skipping ${relPath} (file not found, not in index)`);
11021
+ continue;
11022
+ }
11023
+ for (const entry2 of entries) {
10879
11024
  try {
10880
11025
  if (entry2.type === "page") {
10881
11026
  await wikiService.delete(`pages/${entry2.id}`, { lang: entry2.lang });
@@ -10884,13 +11029,11 @@ async function push(flags, files) {
10884
11029
  }
10885
11030
  const idx = index.indexOf(entry2);
10886
11031
  if (idx !== -1) index.splice(idx, 1);
10887
- indexMap.delete(relPath);
10888
- console.log(` Deleted: ${relPath}`);
11032
+ indexMap.delete(entry2.path);
11033
+ console.log(` Deleted: ${entry2.path}`);
10889
11034
  } catch (err) {
10890
- console.error(` Failed to delete ${relPath}: ${err instanceof Error ? err.message : err}`);
11035
+ console.error(` Failed to delete ${entry2.path}: ${err instanceof Error ? err.message : err}`);
10891
11036
  }
10892
- } else {
10893
- console.log(` Skipping ${relPath} (file not found, not in index)`);
10894
11037
  }
10895
11038
  continue;
10896
11039
  }
@@ -11023,6 +11166,7 @@ async function statusRemote(root, config2, index, flags) {
11023
11166
  const modifiedLocally = [];
11024
11167
  const modifiedOnServer = [];
11025
11168
  const conflict = [];
11169
+ const deletedLocally = [];
11026
11170
  const localOnly = [];
11027
11171
  const serverOnly = [];
11028
11172
  for (const lang of langs) {
@@ -11040,7 +11184,11 @@ async function statusRemote(root, config2, index, flags) {
11040
11184
  serverOnly.push(relPath);
11041
11185
  } else {
11042
11186
  const absPath = path4.join(root, relPath);
11043
- const localChanged = fs5.existsSync(absPath) && computePageHash(absPath) !== localEntry.hash;
11187
+ if (!fs5.existsSync(absPath)) {
11188
+ deletedLocally.push(relPath);
11189
+ continue;
11190
+ }
11191
+ const localChanged = computePageHash(absPath) !== localEntry.hash;
11044
11192
  const serverChanged = page.date_created != null && localEntry.serverDate != null && page.date_created !== localEntry.serverDate;
11045
11193
  if (localChanged && serverChanged) {
11046
11194
  conflict.push(relPath);
@@ -11052,7 +11200,7 @@ async function statusRemote(root, config2, index, flags) {
11052
11200
  }
11053
11201
  }
11054
11202
  for (const [relPath, entry] of localPages) {
11055
- if (entry.lang === lang && !serverPagePaths.has(relPath)) {
11203
+ if (entry.lang === lang && !serverPagePaths.has(relPath) && fs5.existsSync(path4.join(root, relPath))) {
11056
11204
  localOnly.push(relPath);
11057
11205
  }
11058
11206
  }
@@ -11071,7 +11219,11 @@ async function statusRemote(root, config2, index, flags) {
11071
11219
  serverOnly.push(relPath);
11072
11220
  } else {
11073
11221
  const absPath = path4.join(root, relPath);
11074
- const localChanged = fs5.existsSync(absPath) && computeFileHash(absPath) !== localEntry.hash;
11222
+ if (!fs5.existsSync(absPath)) {
11223
+ deletedLocally.push(relPath);
11224
+ continue;
11225
+ }
11226
+ const localChanged = computeFileHash(absPath) !== localEntry.hash;
11075
11227
  const serverChanged = file2.date_created != null && localEntry.serverDate != null && file2.date_created !== localEntry.serverDate;
11076
11228
  if (localChanged && serverChanged) {
11077
11229
  conflict.push(relPath);
@@ -11083,12 +11235,12 @@ async function statusRemote(root, config2, index, flags) {
11083
11235
  }
11084
11236
  }
11085
11237
  for (const [relPath] of localFiles) {
11086
- if (!serverFilePaths.has(relPath)) {
11238
+ if (!serverFilePaths.has(relPath) && fs5.existsSync(path4.join(root, relPath))) {
11087
11239
  localOnly.push(relPath);
11088
11240
  }
11089
11241
  }
11090
11242
  const newFiles = detectNewFiles2(root, index);
11091
- printRemoteStatus("Wiki", root, config2, flags, { modifiedLocally, modifiedOnServer, conflict, new: newFiles, localOnly, serverOnly });
11243
+ printRemoteStatus("Wiki", root, config2, flags, { modifiedLocally, modifiedOnServer, conflict, new: newFiles, deletedLocally, localOnly, serverOnly });
11092
11244
  }
11093
11245
  function watch3(flags) {
11094
11246
  return watch2("wiki", flags, push);
@@ -11126,11 +11278,11 @@ async function readContent(flags) {
11126
11278
  return flags.content;
11127
11279
  }
11128
11280
  if (!process.stdin.isTTY) {
11129
- return new Promise((resolve7) => {
11281
+ return new Promise((resolve8) => {
11130
11282
  let data = "";
11131
11283
  process.stdin.setEncoding("utf-8");
11132
11284
  process.stdin.on("data", (chunk) => data += chunk);
11133
- process.stdin.on("end", () => resolve7(data));
11285
+ process.stdin.on("end", () => resolve8(data));
11134
11286
  });
11135
11287
  }
11136
11288
  console.error("No content provided. Use --file, --content, or pipe to stdin.");
@@ -11337,12 +11489,15 @@ async function pull2(flags) {
11337
11489
  if (allThreads.length > threads.length) {
11338
11490
  console.log(` Skipped ${allThreads.length - threads.length} shared thread(s) from another accounting`);
11339
11491
  }
11492
+ const previousIndex = readIndex(dir);
11340
11493
  const index = [];
11494
+ const serverPaths = /* @__PURE__ */ new Set();
11341
11495
  let pulled = 0;
11342
11496
  for (const thread of threads) {
11343
11497
  if (!thread.id_thread || !thread.type || !thread.description) continue;
11344
11498
  const relPath = threadToRelPath(thread);
11345
11499
  const absPath = path5.join(dir, relPath);
11500
+ serverPaths.add(relPath);
11346
11501
  try {
11347
11502
  const content4 = await etlService.get(
11348
11503
  `threads/${thread.id_thread}/content`,
@@ -11396,6 +11551,12 @@ async function pull2(flags) {
11396
11551
  if (!flags["no-context"]) {
11397
11552
  await pullContext(dir, etlService, defService);
11398
11553
  }
11554
+ removeVanishedFiles(
11555
+ dir,
11556
+ previousIndex,
11557
+ serverPaths,
11558
+ (absPath) => computeHash(fs7.readFileSync(absPath, "utf-8"))
11559
+ );
11399
11560
  writeConfig(dir, {
11400
11561
  domain: conn.domain,
11401
11562
  accounting: conn.tokens.accounting,
@@ -11449,19 +11610,21 @@ async function push2(flags, files) {
11449
11610
  for (const relPath of filesToPush) {
11450
11611
  const absPath = path5.join(root, relPath);
11451
11612
  if (!fs7.existsSync(absPath)) {
11452
- const entry2 = indexMap.get(relPath);
11453
- if (entry2) {
11613
+ const entries = entriesToDelete(root, index, relPath);
11614
+ if (entries.length === 0) {
11615
+ console.log(` Skipping ${relPath} (file not found, not in index)`);
11616
+ continue;
11617
+ }
11618
+ for (const entry2 of entries) {
11454
11619
  try {
11455
11620
  await etlService.delete(`threads/${entry2.id_thread}`);
11456
11621
  const idx = index.indexOf(entry2);
11457
11622
  if (idx !== -1) index.splice(idx, 1);
11458
- indexMap.delete(relPath);
11459
- console.log(` Deleted: ${relPath}`);
11623
+ indexMap.delete(entry2.path);
11624
+ console.log(` Deleted: ${entry2.path}`);
11460
11625
  } catch (err) {
11461
- console.error(` Failed to delete ${relPath}: ${err instanceof Error ? err.message : err}`);
11626
+ console.error(` Failed to delete ${entry2.path}: ${err instanceof Error ? err.message : err}`);
11462
11627
  }
11463
- } else {
11464
- console.log(` Skipping ${relPath} (file not found, not in index)`);
11465
11628
  }
11466
11629
  continue;
11467
11630
  }
@@ -11599,6 +11762,7 @@ async function statusRemote2(root, config2, index, flags) {
11599
11762
  const modifiedLocally = [];
11600
11763
  const modifiedOnServer = [];
11601
11764
  const conflict = [];
11765
+ const deletedLocally = [];
11602
11766
  const localOnly = [];
11603
11767
  const serverOnly = [];
11604
11768
  const serverPaths = /* @__PURE__ */ new Set();
@@ -11611,7 +11775,11 @@ async function statusRemote2(root, config2, index, flags) {
11611
11775
  serverOnly.push(relPath);
11612
11776
  } else {
11613
11777
  const absPath = path5.join(root, relPath);
11614
- const localChanged = fs7.existsSync(absPath) && computeHash(fs7.readFileSync(absPath, "utf-8")) !== localEntry.hash;
11778
+ if (!fs7.existsSync(absPath)) {
11779
+ deletedLocally.push(relPath);
11780
+ continue;
11781
+ }
11782
+ const localChanged = computeHash(fs7.readFileSync(absPath, "utf-8")) !== localEntry.hash;
11615
11783
  const serverChanged = thread.date_created != null && localEntry.serverDate != null && thread.date_created !== localEntry.serverDate;
11616
11784
  if (localChanged && serverChanged) {
11617
11785
  conflict.push(relPath);
@@ -11623,12 +11791,12 @@ async function statusRemote2(root, config2, index, flags) {
11623
11791
  }
11624
11792
  }
11625
11793
  for (const entry of index) {
11626
- if (!serverPaths.has(entry.path)) {
11794
+ if (!serverPaths.has(entry.path) && fs7.existsSync(path5.join(root, entry.path))) {
11627
11795
  localOnly.push(entry.path);
11628
11796
  }
11629
11797
  }
11630
11798
  const newFiles = detectNewFiles3(root, index);
11631
- printRemoteStatus("ETL", root, config2, flags, { modifiedLocally, modifiedOnServer, conflict, new: newFiles, localOnly, serverOnly });
11799
+ printRemoteStatus("ETL", root, config2, flags, { modifiedLocally, modifiedOnServer, conflict, new: newFiles, deletedLocally, localOnly, serverOnly });
11632
11800
  }
11633
11801
  function watch4(flags) {
11634
11802
  return watch2("etl", flags, push2);
@@ -11664,8 +11832,14 @@ async function exec2(id, flags) {
11664
11832
  csv_delimiter: " ",
11665
11833
  csv_header: "1",
11666
11834
  csv_header_json: "1",
11667
- csv_null_value: "NULL"
11835
+ csv_null_value: "NULL",
11836
+ // makes the server flush the response headers before the query finishes, so X-Spid
11837
+ // is readable while it still runs (IIS holds headers back until the first body flush)
11838
+ flush_headers: "1"
11668
11839
  }, { headers: { accept: "text/csv" } });
11840
+ const spid = response.headers.get("x-spid");
11841
+ if (spid) fs8.writeSync(2, `${SPID_MARKER} ${spid}
11842
+ `);
11669
11843
  if (!response.ok) {
11670
11844
  const text3 = await response.text();
11671
11845
  console.error(text3);
@@ -11678,6 +11852,11 @@ async function exec2(id, flags) {
11678
11852
  process.stdout.write(result || "");
11679
11853
  }
11680
11854
  }
11855
+ async function abort2(spid, flags) {
11856
+ const service = await createService("etl", { cwd: flags.dir || "." });
11857
+ const result = await service.get(`abort/${spid}`);
11858
+ output(result, flags);
11859
+ }
11681
11860
  async function search2(query, flags) {
11682
11861
  const service = await createService("etl");
11683
11862
  const results = await service.get("search", { ...getListParams(flags, DEFAULT_FIELDS), q: query });
@@ -11731,6 +11910,12 @@ async function etl(args) {
11731
11910
  process.exit(1);
11732
11911
  }
11733
11912
  return exec2(rest[0], flags);
11913
+ case "abort":
11914
+ if (!rest[0]) {
11915
+ console.error("Usage: rpbi etl abort <spid>");
11916
+ process.exit(1);
11917
+ }
11918
+ return abort2(rest[0], flags);
11734
11919
  case "search":
11735
11920
  if (!rest[0]) {
11736
11921
  console.error("Usage: rpbi etl search <query>");
@@ -11754,7 +11939,7 @@ async function etl(args) {
11754
11939
  case "watch":
11755
11940
  return watch4(flags);
11756
11941
  default:
11757
- console.log("Usage: rpbi etl <list|get|content|exec|search|params|pull|push|context|status|watch> [options]");
11942
+ console.log("Usage: rpbi etl <list|get|content|exec|abort|search|params|pull|push|context|status|watch> [options]");
11758
11943
  break;
11759
11944
  }
11760
11945
  }
@@ -11822,12 +12007,15 @@ async function pull3(flags) {
11822
12007
  order: [["type", "asc"], ["description", "asc"]]
11823
12008
  }) || [];
11824
12009
  console.log(`Found ${threads.length} threads`);
12010
+ const previousIndex = readIndex(dir);
11825
12011
  const index = [];
12012
+ const serverPaths = /* @__PURE__ */ new Set();
11826
12013
  let pulled = 0;
11827
12014
  for (const thread of threads) {
11828
12015
  if (!thread.id_thread || !thread.type || !thread.description) continue;
11829
12016
  const relPath = threadToRelPath2(thread);
11830
12017
  const absPath = path6.join(dir, relPath);
12018
+ serverPaths.add(relPath);
11831
12019
  try {
11832
12020
  const content4 = await service.get(
11833
12021
  `threads/${thread.id_thread}/content`,
@@ -11851,6 +12039,7 @@ async function pull3(flags) {
11851
12039
  }
11852
12040
  if (thread.has_doc) {
11853
12041
  const docPath = threadToDocPath(thread);
12042
+ serverPaths.add(docPath);
11854
12043
  try {
11855
12044
  const doc2 = await service.get(
11856
12045
  `threads/${thread.id_thread}/doc`,
@@ -11874,6 +12063,12 @@ async function pull3(flags) {
11874
12063
  }
11875
12064
  }
11876
12065
  }
12066
+ removeVanishedFiles(
12067
+ dir,
12068
+ previousIndex,
12069
+ serverPaths,
12070
+ (absPath) => computeHash(fs9.readFileSync(absPath, "utf-8"))
12071
+ );
11877
12072
  writeConfig(dir, {
11878
12073
  domain: conn.domain,
11879
12074
  accounting: conn.tokens.accounting,
@@ -11913,25 +12108,28 @@ async function push3(flags, files) {
11913
12108
  const absPath = path6.join(root, relPath);
11914
12109
  const entry = indexMap.get(relPath);
11915
12110
  if (!fs9.existsSync(absPath)) {
11916
- if (!entry) {
12111
+ const entries = entriesToDelete(root, index, relPath);
12112
+ if (entries.length === 0) {
11917
12113
  console.log(` Skipping ${relPath} (file not found, not in index)`);
11918
12114
  continue;
11919
12115
  }
11920
- try {
11921
- if (entry.kind === "doc") {
11922
- await service.patch(`threads/${entry.id_thread}/doc`, "", { accounting: entryAccounting(entry) });
11923
- console.log(` Cleared doc: ${relPath}`);
11924
- } else {
11925
- const current = await service.get(`threads/${entry.id_thread}`, { fields: ["idz"] });
11926
- if (!current?.idz) throw new Error("Thread not found on server");
11927
- await service.delete(`threads/${current.idz}`, { accounting: entryAccounting(entry) });
11928
- console.log(` Deleted: ${relPath}`);
11929
- }
11930
- const idx = index.indexOf(entry);
11931
- if (idx !== -1) index.splice(idx, 1);
11932
- indexMap.delete(relPath);
11933
- } catch (err) {
11934
- console.error(` Failed to delete ${relPath}: ${err instanceof Error ? err.message : err}`);
12116
+ for (const gone of entries) {
12117
+ try {
12118
+ if (gone.kind === "doc") {
12119
+ await service.patch(`threads/${gone.id_thread}/doc`, "", { accounting: entryAccounting(gone) });
12120
+ console.log(` Cleared doc: ${gone.path}`);
12121
+ } else {
12122
+ const current = await service.get(`threads/${gone.id_thread}`, { fields: ["idz"] });
12123
+ if (!current?.idz) throw new Error("Thread not found on server");
12124
+ await service.delete(`threads/${current.idz}`, { accounting: entryAccounting(gone) });
12125
+ console.log(` Deleted: ${gone.path}`);
12126
+ }
12127
+ const idx = index.indexOf(gone);
12128
+ if (idx !== -1) index.splice(idx, 1);
12129
+ indexMap.delete(gone.path);
12130
+ } catch (err) {
12131
+ console.error(` Failed to delete ${gone.path}: ${err instanceof Error ? err.message : err}`);
12132
+ }
11935
12133
  }
11936
12134
  continue;
11937
12135
  }
@@ -12048,16 +12246,21 @@ async function statusRemote3(root, config2, index, flags) {
12048
12246
  const modifiedLocally = [];
12049
12247
  const modifiedOnServer = [];
12050
12248
  const conflict = [];
12249
+ const deletedLocally = [];
12051
12250
  const localOnly = [];
12052
12251
  const serverOnly = [];
12053
- const compareEntry = (relPath, serverDate, isServerOnly) => {
12252
+ const compareEntry = (relPath, serverDate, onServer) => {
12054
12253
  const localEntry = indexMap.get(relPath);
12055
12254
  if (!localEntry) {
12056
- if (isServerOnly) serverOnly.push(relPath);
12255
+ if (onServer) serverOnly.push(relPath);
12057
12256
  return;
12058
12257
  }
12059
12258
  const absPath = path6.join(root, relPath);
12060
- const localChanged = fs9.existsSync(absPath) && computeHash(fs9.readFileSync(absPath, "utf-8")) !== localEntry.hash;
12259
+ if (!fs9.existsSync(absPath)) {
12260
+ if (onServer) deletedLocally.push(relPath);
12261
+ return;
12262
+ }
12263
+ const localChanged = computeHash(fs9.readFileSync(absPath, "utf-8")) !== localEntry.hash;
12061
12264
  const serverChanged = serverDate != null && localEntry.serverDate != null && serverDate !== localEntry.serverDate;
12062
12265
  if (localChanged && serverChanged) {
12063
12266
  conflict.push(relPath);
@@ -12083,12 +12286,12 @@ async function statusRemote3(root, config2, index, flags) {
12083
12286
  }
12084
12287
  }
12085
12288
  for (const entry of index) {
12086
- if (!serverPaths.has(entry.path)) {
12289
+ if (!serverPaths.has(entry.path) && fs9.existsSync(path6.join(root, entry.path))) {
12087
12290
  localOnly.push(entry.path);
12088
12291
  }
12089
12292
  }
12090
12293
  const newFiles = detectNewFiles4(root, index);
12091
- printRemoteStatus("Deployment", root, config2, flags, { modifiedLocally, modifiedOnServer, conflict, new: newFiles, localOnly, serverOnly });
12294
+ printRemoteStatus("Deployment", root, config2, flags, { modifiedLocally, modifiedOnServer, conflict, new: newFiles, deletedLocally, localOnly, serverOnly });
12092
12295
  }
12093
12296
  function watch5(flags) {
12094
12297
  return watch2("deployment", flags, push3);
@@ -12282,7 +12485,9 @@ async function pull4(flags) {
12282
12485
  const projects2 = await fetchProjects(service);
12283
12486
  const projectNames = buildProjectMap(projects2);
12284
12487
  console.log(`Found ${projects2.length} projects`);
12488
+ const previousIndex = readIndex(dir);
12285
12489
  const index = [];
12490
+ const serverPaths = /* @__PURE__ */ new Set();
12286
12491
  const resources2 = await service.get("resources", {
12287
12492
  fields: RESOURCE_FIELDS,
12288
12493
  order: [["name", "asc"]]
@@ -12297,6 +12502,7 @@ async function pull4(flags) {
12297
12502
  continue;
12298
12503
  }
12299
12504
  const relPath = resourceToRelPath(projectName, resource);
12505
+ serverPaths.add(relPath);
12300
12506
  try {
12301
12507
  const content4 = await service.get(
12302
12508
  `resourceContent/${resource.id_custom_resource}`,
@@ -12328,6 +12534,7 @@ async function pull4(flags) {
12328
12534
  const projectName = query.id_custom_page ? projectNames.get(query.id_custom_page) : void 0;
12329
12535
  if (!projectName) continue;
12330
12536
  const relPath = queryToRelPath(projectName, query);
12537
+ serverPaths.add(relPath);
12331
12538
  try {
12332
12539
  const content4 = await service.get(
12333
12540
  `queryContent/${query.id_thread}`,
@@ -12354,6 +12561,12 @@ async function pull4(flags) {
12354
12561
  if (!project.id_custom_page || !project.name) continue;
12355
12562
  await refreshProjectMeta(service, dir, project, queries2.filter((q) => q.id_custom_page === project.id_custom_page));
12356
12563
  }
12564
+ removeVanishedFiles(
12565
+ dir,
12566
+ previousIndex,
12567
+ serverPaths,
12568
+ (absPath) => computeHash(fs11.readFileSync(absPath, "utf-8"))
12569
+ );
12357
12570
  writeConfig(dir, {
12358
12571
  domain: conn.domain,
12359
12572
  accounting: conn.tokens.accounting,
@@ -12406,18 +12619,21 @@ async function push4(flags, files) {
12406
12619
  const absPath = path7.join(root, relPath);
12407
12620
  const entry = indexMap.get(relPath);
12408
12621
  if (!fs11.existsSync(absPath)) {
12409
- if (!entry) {
12622
+ const entries = entriesToDelete(root, index, relPath);
12623
+ if (entries.length === 0) {
12410
12624
  console.log(` Skipping ${relPath} (file not found, not in index)`);
12411
12625
  continue;
12412
12626
  }
12413
- try {
12414
- await service.delete(entry.kind === "query" ? `queries/${entry.id}` : `resources/${entry.id}`);
12415
- const idx = index.indexOf(entry);
12416
- if (idx !== -1) index.splice(idx, 1);
12417
- indexMap.delete(relPath);
12418
- console.log(` Deleted: ${relPath}`);
12419
- } catch (err) {
12420
- console.error(` Failed to delete ${relPath}: ${err instanceof Error ? err.message : err}`);
12627
+ for (const gone of entries) {
12628
+ try {
12629
+ await service.delete(gone.kind === "query" ? `queries/${gone.id}` : `resources/${gone.id}`);
12630
+ const idx = index.indexOf(gone);
12631
+ if (idx !== -1) index.splice(idx, 1);
12632
+ indexMap.delete(gone.path);
12633
+ console.log(` Deleted: ${gone.path}`);
12634
+ } catch (err) {
12635
+ console.error(` Failed to delete ${gone.path}: ${err instanceof Error ? err.message : err}`);
12636
+ }
12421
12637
  }
12422
12638
  continue;
12423
12639
  }
@@ -12532,6 +12748,7 @@ async function statusRemote4(root, config2, index, flags) {
12532
12748
  const modifiedLocally = [];
12533
12749
  const modifiedOnServer = [];
12534
12750
  const conflict = [];
12751
+ const deletedLocally = [];
12535
12752
  const localOnly = [];
12536
12753
  const serverOnly = [];
12537
12754
  const compareEntry = (relPath, serverDate) => {
@@ -12541,7 +12758,11 @@ async function statusRemote4(root, config2, index, flags) {
12541
12758
  return;
12542
12759
  }
12543
12760
  const absPath = path7.join(root, relPath);
12544
- const localChanged = fs11.existsSync(absPath) && computeHash(fs11.readFileSync(absPath, "utf-8")) !== localEntry.hash;
12761
+ if (!fs11.existsSync(absPath)) {
12762
+ deletedLocally.push(relPath);
12763
+ return;
12764
+ }
12765
+ const localChanged = computeHash(fs11.readFileSync(absPath, "utf-8")) !== localEntry.hash;
12545
12766
  const serverChanged = serverDate != null && localEntry.serverDate != null && serverDate !== localEntry.serverDate;
12546
12767
  if (localChanged && serverChanged) {
12547
12768
  conflict.push(relPath);
@@ -12571,12 +12792,12 @@ async function statusRemote4(root, config2, index, flags) {
12571
12792
  compareEntry(relPath, query.date_created);
12572
12793
  }
12573
12794
  for (const entry of index) {
12574
- if (!serverPaths.has(entry.path)) {
12795
+ if (!serverPaths.has(entry.path) && fs11.existsSync(path7.join(root, entry.path))) {
12575
12796
  localOnly.push(entry.path);
12576
12797
  }
12577
12798
  }
12578
12799
  const newFiles = detectNewFiles5(root, index);
12579
- printRemoteStatus("Extensions", root, config2, flags, { modifiedLocally, modifiedOnServer, conflict, new: newFiles, localOnly, serverOnly });
12800
+ printRemoteStatus("Extensions", root, config2, flags, { modifiedLocally, modifiedOnServer, conflict, new: newFiles, deletedLocally, localOnly, serverOnly });
12580
12801
  }
12581
12802
  function watch6(flags) {
12582
12803
  return watch2("ext", flags, push4);
@@ -12607,18 +12828,24 @@ async function content3(id, flags) {
12607
12828
  const text2 = await service.get(endpoint, void 0, { parseResponse: false });
12608
12829
  process.stdout.write(text2 || "");
12609
12830
  }
12831
+ async function refreshMetaForProject(service, flags, idProject) {
12832
+ if (!idProject) return;
12833
+ const root = findProjectRoot(flags.dir || ".");
12834
+ if (!root || readConfig(root)?.type !== "ext") return;
12835
+ try {
12836
+ const project = await service.get(`projects/${idProject}`);
12837
+ if (!project?.name) return;
12838
+ await refreshProjectMeta(service, root, project);
12839
+ console.error(`Updated .rpbi/projects/${project.name}.yaml`);
12840
+ } catch {
12841
+ }
12842
+ }
12610
12843
  async function params2(id, flags) {
12611
12844
  const service = await createService("extensions", { cwd: flags.dir || "." });
12612
12845
  const refreshMeta = async () => {
12613
- const root = findProjectRoot(flags.dir || ".");
12614
- if (!root || readConfig(root)?.type !== "ext") return;
12615
12846
  try {
12616
12847
  const query = await service.get(`queries/${id}`, { fields: ["id_custom_page"] });
12617
- if (!query?.id_custom_page) return;
12618
- const project = await service.get(`projects/${query.id_custom_page}`);
12619
- if (!project?.name) return;
12620
- await refreshProjectMeta(service, root, project);
12621
- console.error(`Updated .rpbi/projects/${project.name}.yaml`);
12848
+ await refreshMetaForProject(service, flags, query?.id_custom_page);
12622
12849
  } catch {
12623
12850
  }
12624
12851
  };
@@ -12637,6 +12864,53 @@ async function params2(id, flags) {
12637
12864
  output(result || [], flags);
12638
12865
  }
12639
12866
  }
12867
+ var DEFAULT_DEP_FIELDS = ["idz", "id_custom_page", "id_custom_resource", "resource_name", "id_thread", "thread_name"];
12868
+ async function deps(flags) {
12869
+ const service = await createService("extensions", { cwd: flags.dir || "." });
12870
+ if (flags.add) {
12871
+ if (!flags.query && !flags.resource) {
12872
+ console.error("Usage: rpbi ext deps --query <id> --add <resource_id> | --resource <id> --add <query_id>");
12873
+ process.exit(1);
12874
+ }
12875
+ const idThread = flags.query ? parseInt(flags.query) : parseInt(flags.add);
12876
+ const idResource = flags.resource ? parseInt(flags.resource) : parseInt(flags.add);
12877
+ const created = await service.post("queryDependencies", {
12878
+ id_thread: idThread,
12879
+ id_custom_resource: idResource
12880
+ });
12881
+ try {
12882
+ const query = await service.get(`queries/${idThread}`, { fields: ["id_custom_page"] });
12883
+ await refreshMetaForProject(service, flags, query?.id_custom_page);
12884
+ } catch {
12885
+ }
12886
+ output(created, flags);
12887
+ } else if (flags.remove) {
12888
+ let idProject;
12889
+ try {
12890
+ const rows = await service.get("queryDependencies", { where: { idz: parseInt(flags.remove) } });
12891
+ idProject = rows?.[0]?.id_custom_page;
12892
+ } catch {
12893
+ }
12894
+ await service.delete(`queryDependencies/${flags.remove}`);
12895
+ await refreshMetaForProject(service, flags, idProject);
12896
+ console.log(`Dependency ${flags.remove} deleted.`);
12897
+ } else {
12898
+ let where = null;
12899
+ if (flags.query) where = { id_thread: parseInt(flags.query) };
12900
+ else if (flags.resource) where = { id_custom_resource: parseInt(flags.resource) };
12901
+ else if (flags.project) where = { id_custom_page: parseInt(flags.project) };
12902
+ if (!where) {
12903
+ console.error("Usage: rpbi ext deps --project <id> | --query <id> | --resource <id> [--add <id>] [--remove <idz>]");
12904
+ process.exit(1);
12905
+ }
12906
+ const result = await service.get("queryDependencies", {
12907
+ ...getListParams(flags, DEFAULT_DEP_FIELDS),
12908
+ where,
12909
+ order: [["resource_name", "asc"], ["thread_name", "asc"]]
12910
+ });
12911
+ output(result || [], flags);
12912
+ }
12913
+ }
12640
12914
  async function exec4(id, flags) {
12641
12915
  const service = await createService("extensions", { cwd: flags.dir || "." });
12642
12916
  let body = void 0;
@@ -12650,8 +12924,14 @@ async function exec4(id, flags) {
12650
12924
  csv_delimiter: " ",
12651
12925
  csv_header: "1",
12652
12926
  csv_header_json: "1",
12653
- csv_null_value: "NULL"
12927
+ csv_null_value: "NULL",
12928
+ // makes the server flush the response headers before the query finishes, so X-Spid
12929
+ // is readable while it still runs (IIS holds headers back until the first body flush)
12930
+ flush_headers: "1"
12654
12931
  }, { headers: { accept: "text/csv" } });
12932
+ const spid = response.headers.get("x-spid");
12933
+ if (spid) fs12.writeSync(2, `${SPID_MARKER} ${spid}
12934
+ `);
12655
12935
  if (!response.ok) {
12656
12936
  const text3 = await response.text();
12657
12937
  console.error(text3);
@@ -12664,6 +12944,11 @@ async function exec4(id, flags) {
12664
12944
  process.stdout.write(result || "");
12665
12945
  }
12666
12946
  }
12947
+ async function abort3(spid, flags) {
12948
+ const service = await createService("extensions", { cwd: flags.dir || "." });
12949
+ const result = await service.get(`abort/${spid}`);
12950
+ output(result, flags);
12951
+ }
12667
12952
  async function ext(args) {
12668
12953
  const { positional, flags } = parseArgs(args);
12669
12954
  const [subcommand, ...rest] = positional;
@@ -12686,12 +12971,20 @@ async function ext(args) {
12686
12971
  process.exit(1);
12687
12972
  }
12688
12973
  return exec4(rest[0], flags);
12974
+ case "abort":
12975
+ if (!rest[0]) {
12976
+ console.error("Usage: rpbi ext abort <spid>");
12977
+ process.exit(1);
12978
+ }
12979
+ return abort3(rest[0], flags);
12689
12980
  case "params":
12690
12981
  if (!rest[0]) {
12691
12982
  console.error("Usage: rpbi ext params <query_id> [--save <json file>] [--delete <idz>]");
12692
12983
  process.exit(1);
12693
12984
  }
12694
12985
  return params2(rest[0], flags);
12986
+ case "deps":
12987
+ return deps(flags);
12695
12988
  case "pull":
12696
12989
  return pull4(flags);
12697
12990
  case "push":
@@ -12701,7 +12994,7 @@ async function ext(args) {
12701
12994
  case "watch":
12702
12995
  return watch6(flags);
12703
12996
  default:
12704
- console.log("Usage: rpbi ext <projects|resources|queries|content|exec|params|pull|push|status|watch> [options]");
12997
+ console.log("Usage: rpbi ext <projects|resources|queries|content|exec|abort|params|deps|pull|push|status|watch> [options]");
12705
12998
  break;
12706
12999
  }
12707
13000
  }
@@ -17311,7 +17604,7 @@ __export(util_exports, {
17311
17604
  getParsedType: () => getParsedType2,
17312
17605
  getSizableOrigin: () => getSizableOrigin,
17313
17606
  hexToUint8Array: () => hexToUint8Array,
17314
- isObject: () => isObject,
17607
+ isObject: () => isObject2,
17315
17608
  isPlainObject: () => isPlainObject,
17316
17609
  issue: () => issue,
17317
17610
  joinValues: () => joinValues,
@@ -17481,7 +17774,7 @@ function slugify(input) {
17481
17774
  }
17482
17775
  var captureStackTrace = "captureStackTrace" in Error ? Error.captureStackTrace : (..._args) => {
17483
17776
  };
17484
- function isObject(data) {
17777
+ function isObject2(data) {
17485
17778
  return typeof data === "object" && data !== null && !Array.isArray(data);
17486
17779
  }
17487
17780
  var allowsEval = cached(() => {
@@ -17497,7 +17790,7 @@ var allowsEval = cached(() => {
17497
17790
  }
17498
17791
  });
17499
17792
  function isPlainObject(o) {
17500
- if (isObject(o) === false)
17793
+ if (isObject2(o) === false)
17501
17794
  return false;
17502
17795
  const ctor = o.constructor;
17503
17796
  if (ctor === void 0)
@@ -17505,7 +17798,7 @@ function isPlainObject(o) {
17505
17798
  if (typeof ctor !== "function")
17506
17799
  return true;
17507
17800
  const prot = ctor.prototype;
17508
- if (isObject(prot) === false)
17801
+ if (isObject2(prot) === false)
17509
17802
  return false;
17510
17803
  if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) {
17511
17804
  return false;
@@ -19663,13 +19956,13 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def2) => {
19663
19956
  }
19664
19957
  return propValues;
19665
19958
  });
19666
- const isObject2 = isObject;
19959
+ const isObject3 = isObject2;
19667
19960
  const catchall = def2.catchall;
19668
19961
  let value;
19669
19962
  inst._zod.parse = (payload, ctx) => {
19670
19963
  value ?? (value = _normalized.value);
19671
19964
  const input = payload.value;
19672
- if (!isObject2(input)) {
19965
+ if (!isObject3(input)) {
19673
19966
  payload.issues.push({
19674
19967
  expected: "object",
19675
19968
  code: "invalid_type",
@@ -19767,7 +20060,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def2) =
19767
20060
  return (payload, ctx) => fn(shape, payload, ctx);
19768
20061
  };
19769
20062
  let fastpass;
19770
- const isObject2 = isObject;
20063
+ const isObject3 = isObject2;
19771
20064
  const jit = !globalConfig.jitless;
19772
20065
  const allowsEval2 = allowsEval;
19773
20066
  const fastEnabled = jit && allowsEval2.value;
@@ -19776,7 +20069,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def2) =
19776
20069
  inst._zod.parse = (payload, ctx) => {
19777
20070
  value ?? (value = _normalized.value);
19778
20071
  const input = payload.value;
19779
- if (!isObject2(input)) {
20072
+ if (!isObject3(input)) {
19780
20073
  payload.issues.push({
19781
20074
  expected: "object",
19782
20075
  code: "invalid_type",
@@ -19954,7 +20247,7 @@ var $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnio
19954
20247
  });
19955
20248
  inst._zod.parse = (payload, ctx) => {
19956
20249
  const input = payload.value;
19957
- if (!isObject(input)) {
20250
+ if (!isObject2(input)) {
19958
20251
  payload.issues.push({
19959
20252
  code: "invalid_type",
19960
20253
  expected: "object",
@@ -34222,7 +34515,7 @@ var Protocol = class {
34222
34515
  return;
34223
34516
  }
34224
34517
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
34225
- await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
34518
+ await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
34226
34519
  options?.signal?.throwIfAborted();
34227
34520
  }
34228
34521
  } catch (error48) {
@@ -34239,7 +34532,7 @@ var Protocol = class {
34239
34532
  */
34240
34533
  request(request, resultSchema, options) {
34241
34534
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
34242
- return new Promise((resolve7, reject) => {
34535
+ return new Promise((resolve8, reject) => {
34243
34536
  const earlyReject = (error48) => {
34244
34537
  reject(error48);
34245
34538
  };
@@ -34317,7 +34610,7 @@ var Protocol = class {
34317
34610
  if (!parseResult.success) {
34318
34611
  reject(parseResult.error);
34319
34612
  } else {
34320
- resolve7(parseResult.data);
34613
+ resolve8(parseResult.data);
34321
34614
  }
34322
34615
  } catch (error48) {
34323
34616
  reject(error48);
@@ -34578,12 +34871,12 @@ var Protocol = class {
34578
34871
  }
34579
34872
  } catch {
34580
34873
  }
34581
- return new Promise((resolve7, reject) => {
34874
+ return new Promise((resolve8, reject) => {
34582
34875
  if (signal.aborted) {
34583
34876
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
34584
34877
  return;
34585
34878
  }
34586
- const timeoutId = setTimeout(resolve7, interval);
34879
+ const timeoutId = setTimeout(resolve8, interval);
34587
34880
  signal.addEventListener("abort", () => {
34588
34881
  clearTimeout(timeoutId);
34589
34882
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -34678,7 +34971,7 @@ function mergeCapabilities(base, additional) {
34678
34971
 
34679
34972
  // ../../.yarn/__virtual__/@modelcontextprotocol-sdk-virtual-4beea6215a/0/cache/@modelcontextprotocol-sdk-npm-1.26.0-380479a6f8-b4098789d9.zip/node_modules/@modelcontextprotocol/sdk/dist/esm/validation/ajv-provider.js
34680
34973
  var import_ajv = __toESM(require_ajv(), 1);
34681
- var import_ajv_formats = __toESM(require_dist2(), 1);
34974
+ var import_ajv_formats = __toESM(require_dist(), 1);
34682
34975
  function createDefaultAjvInstance() {
34683
34976
  const ajv = new import_ajv.default({
34684
34977
  strict: false,
@@ -35542,7 +35835,7 @@ var McpServer = class {
35542
35835
  let task = createTaskResult.task;
35543
35836
  const pollInterval = task.pollInterval ?? 5e3;
35544
35837
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
35545
- await new Promise((resolve7) => setTimeout(resolve7, pollInterval));
35838
+ await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
35546
35839
  const updatedTask = await extra.taskStore.getTask(taskId);
35547
35840
  if (!updatedTask) {
35548
35841
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -36185,12 +36478,12 @@ var StdioServerTransport = class {
36185
36478
  this.onclose?.();
36186
36479
  }
36187
36480
  send(message) {
36188
- return new Promise((resolve7) => {
36481
+ return new Promise((resolve8) => {
36189
36482
  const json3 = serializeMessage(message);
36190
36483
  if (this._stdout.write(json3)) {
36191
- resolve7();
36484
+ resolve8();
36192
36485
  } else {
36193
- this._stdout.once("drain", resolve7);
36486
+ this._stdout.once("drain", resolve8);
36194
36487
  }
36195
36488
  });
36196
36489
  }
@@ -36228,31 +36521,19 @@ function text(data) {
36228
36521
  return { content: [{ type: "text", text: data }] };
36229
36522
  }
36230
36523
  async function mcp() {
36524
+ setMcpMode();
36231
36525
  const server = new McpServer({
36232
36526
  name: "reporting-bi",
36233
- version: "0.1.10"
36527
+ version: "0.1.12"
36234
36528
  });
36235
36529
  server.tool(
36236
36530
  "status",
36237
- "Show active connection, accounting, and token expiry for all logged-in domains",
36531
+ "Show active connection, accounting, and token/session expiry for all logged-in domains",
36238
36532
  {},
36239
36533
  async () => {
36240
- const creds = loadCredentials();
36241
- const domains = Object.keys(creds.connections);
36242
- if (domains.length === 0) return text("Not logged in.");
36243
- const result = domains.map((domain2) => {
36244
- const tokens = creds.connections[domain2];
36245
- const now = Math.floor(Date.now() / 1e3);
36246
- const expiresIn = tokens.expires_at - now;
36247
- return {
36248
- domain: domain2,
36249
- active: creds.active === domain2,
36250
- accounting: tokens.accounting ?? null,
36251
- token_expires_in_seconds: tokens.expires_at === 0 ? null : expiresIn,
36252
- has_refresh_token: !!tokens.refresh_token
36253
- };
36254
- });
36255
- return json2(result);
36534
+ const report2 = statusReport();
36535
+ if (report2.connections.length === 0) return text("Not logged in.");
36536
+ return json2(report2);
36256
36537
  }
36257
36538
  );
36258
36539
  server.tool(
@@ -36733,26 +37014,26 @@ function saveUpdateCheck(data) {
36733
37014
  fs13.writeFileSync(UPDATE_CHECK_FILE, JSON.stringify(data, null, 2));
36734
37015
  }
36735
37016
  function fetchLatestVersion() {
36736
- return new Promise((resolve7) => {
37017
+ return new Promise((resolve8) => {
36737
37018
  const req = https.get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { timeout: 5e3 }, (res) => {
36738
37019
  if (res.statusCode !== 200) {
36739
37020
  res.resume();
36740
- return resolve7(null);
37021
+ return resolve8(null);
36741
37022
  }
36742
37023
  let body = "";
36743
37024
  res.on("data", (chunk) => body += chunk);
36744
37025
  res.on("end", () => {
36745
37026
  try {
36746
- resolve7(JSON.parse(body).version ?? null);
37027
+ resolve8(JSON.parse(body).version ?? null);
36747
37028
  } catch {
36748
- resolve7(null);
37029
+ resolve8(null);
36749
37030
  }
36750
37031
  });
36751
37032
  });
36752
- req.on("error", () => resolve7(null));
37033
+ req.on("error", () => resolve8(null));
36753
37034
  req.on("timeout", () => {
36754
37035
  req.destroy();
36755
- resolve7(null);
37036
+ resolve8(null);
36756
37037
  });
36757
37038
  });
36758
37039
  }
@@ -36776,10 +37057,10 @@ async function checkForUpdate() {
36776
37057
  saveUpdateCheck({ lastCheck: now });
36777
37058
  const latest = await fetchLatestVersion();
36778
37059
  if (!latest) return;
36779
- if (!isNewer(latest, "0.1.10")) return;
36780
- console.log(`\x1B[33mUpdating ${PACKAGE_NAME} ${"0.1.10"} \u2192 ${latest}...\x1B[0m`);
36781
- (0, import_node_child_process3.execSync)(npmInstallCmd(latest), { stdio: "inherit" });
36782
- console.log(`\x1B[32mUpdated to ${latest}.\x1B[0m`);
37060
+ if (!isNewer(latest, "0.1.12")) return;
37061
+ console.error(`\x1B[33mUpdating ${PACKAGE_NAME} ${"0.1.12"} \u2192 ${latest}...\x1B[0m`);
37062
+ (0, import_node_child_process3.execSync)(npmInstallCmd(latest), { stdio: ["ignore", 2, 2] });
37063
+ console.error(`\x1B[32mUpdated to ${latest}.\x1B[0m`);
36783
37064
  } catch {
36784
37065
  }
36785
37066
  }
@@ -36789,11 +37070,11 @@ async function forceUpdate() {
36789
37070
  console.error("Could not fetch latest version from npm.");
36790
37071
  process.exit(1);
36791
37072
  }
36792
- if (!isNewer(latest, "0.1.10")) {
36793
- console.log(`Already up to date (${"0.1.10"}).`);
37073
+ if (!isNewer(latest, "0.1.12")) {
37074
+ console.log(`Already up to date (${"0.1.12"}).`);
36794
37075
  return;
36795
37076
  }
36796
- console.log(`Updating ${PACKAGE_NAME} ${"0.1.10"} \u2192 ${latest}...`);
37077
+ console.log(`Updating ${PACKAGE_NAME} ${"0.1.12"} \u2192 ${latest}...`);
36797
37078
  (0, import_node_child_process3.execSync)(npmInstallCmd(latest), { stdio: "inherit" });
36798
37079
  console.log(`\x1B[32mUpdated to ${latest}.\x1B[0m`);
36799
37080
  saveUpdateCheck({ lastCheck: Date.now() });
@@ -36804,11 +37085,20 @@ var USAGE_default = `Reporting.bi CLI
36804
37085
 
36805
37086
  Usage: rpbi <command> [options]
36806
37087
 
37088
+ Global options:
37089
+ --connection <domain> Run against this login instead of the active one
37090
+ --json Machine-readable output
37091
+ --verbose Log every request to stderr
37092
+
37093
+ Workspace sync (etl/wiki/deployment/ext pull, push, status) mirrors deletions both ways:
37094
+ pull removes local files deleted on the server (a locally modified file is kept), push
37095
+ deletes on the server what was deleted locally \u2014 single files or whole folders.
37096
+
36807
37097
  Commands:
36808
37098
  login [domain] [--accounting <id>] Login to a Reporting.bi instance (opens browser)
36809
37099
  logout [domain] Logout and revoke tokens (default: active connection)
36810
37100
  use <domain> [--accounting <id>] Switch active connection and/or set accounting
36811
- status Show all connections and active status
37101
+ status [--json] Show all connections, token and session expiry
36812
37102
  init [--connection <domain>] [--accounting <id>] [--type etl|wiki|deployment|ext] Initialize .rpbi/config.json in current directory
36813
37103
  update Force update to latest version from npm
36814
37104
  mcp Start MCP server (for AI agents, stdio transport)
@@ -36821,6 +37111,7 @@ ETL:
36821
37111
  etl get <id> Get thread metadata
36822
37112
  etl content <id> Get thread SQL content (plain text)
36823
37113
  etl exec <id> [--sql "..."|--file f] Execute thread (optionally with custom SQL)
37114
+ etl abort <spid> Abort a running exec (exec reports "rpbi: spid <n>" on stderr)
36824
37115
  etl search <query> Search threads
36825
37116
  etl params <id> [--save <json>|--delete <idz>] List, save or delete thread parameters
36826
37117
  etl pull [--dir <path>] [--no-context] Pull all ETL threads, parameters, metadata & schema to local files
@@ -36846,7 +37137,11 @@ Extensions (requires the "custom.define" right):
36846
37137
  ext queries List queries
36847
37138
  ext content <id> [--query] Get resource content (--query for query SQL)
36848
37139
  ext exec <query_id> [--sql "..."|--file f] Execute query (optionally with custom SQL)
37140
+ ext abort <spid> Abort a running exec (exec reports "rpbi: spid <n>" on stderr)
36849
37141
  ext params <query_id> [--save <json>|--delete <idz>] List, save or delete query parameters
37142
+ ext deps --project <id>|--query <id>|--resource <id> List SQL dependencies (resource \u2194 query links)
37143
+ ext deps --query <id> --add <resource_id> Link a query to a resource (or --resource <id> --add <query_id>)
37144
+ ext deps --remove <idz> Unlink a dependency
36850
37145
  ext pull [--dir <path>] Pull all projects (resources, queries & project metadata) to local files
36851
37146
  ext push [files...] [--dir <path>] [--force] Push modified/new files back to server
36852
37147
  ext status [--dir <path>] [--remote] [--json] Show local changes (--remote compares with server)
@@ -36967,7 +37262,7 @@ Examples:
36967
37262
  (async () => {
36968
37263
  const [command, ...args] = process.argv.slice(2);
36969
37264
  if (command === "--version" || command === "-v") {
36970
- console.log("0.1.10");
37265
+ console.log("0.1.12");
36971
37266
  return;
36972
37267
  }
36973
37268
  await checkForUpdate();
@@ -37024,7 +37319,7 @@ Examples:
37024
37319
  helpdesk(args);
37025
37320
  break;
37026
37321
  case "status":
37027
- status();
37322
+ status(args);
37028
37323
  break;
37029
37324
  case "mcp":
37030
37325
  mcp();