@qaecy/cue-cli 0.0.46 → 0.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/main.js +450 -30
  2. package/package.json +1 -1
  3. package/readme.md +36 -0
package/main.js CHANGED
@@ -372,7 +372,7 @@ var init_md5_builder_node = __esm({
372
372
  });
373
373
 
374
374
  // apps/desktop/cue-cli/src/main.ts
375
- var import_commander = require("commander");
375
+ var import_commander2 = require("commander");
376
376
  var import_fs8 = require("fs");
377
377
  var import_path4 = require("path");
378
378
 
@@ -4721,6 +4721,53 @@ var prefixCC = {
4721
4721
  rex: "http://purl.obolibrary.org/obo/REX_"
4722
4722
  };
4723
4723
 
4724
+ // libs/js/prefixes/src/lib/compact-expand.ts
4725
+ var CompactExpand = class _CompactExpand {
4726
+ static _instance;
4727
+ // Opposite map is built once
4728
+ _nsMap;
4729
+ static getInstance() {
4730
+ if (this._instance) {
4731
+ return this._instance;
4732
+ }
4733
+ this._instance = new _CompactExpand();
4734
+ return this._instance;
4735
+ }
4736
+ compactIRI(iri) {
4737
+ let nsMap = this._nsMap;
4738
+ if (nsMap === void 0)
4739
+ nsMap = this._buildNSMap();
4740
+ let ns = iri;
4741
+ if (iri.includes("#"))
4742
+ ns = `${iri.split("#")[0]}#`;
4743
+ else {
4744
+ const parts = iri.split("/");
4745
+ parts.pop();
4746
+ ns = parts.join("/") + "/";
4747
+ }
4748
+ if (ns === void 0)
4749
+ return iri;
4750
+ const pfx = nsMap[ns];
4751
+ if (pfx === void 0)
4752
+ return iri;
4753
+ return iri.replace(ns, `${pfx}:`);
4754
+ }
4755
+ expandIRI(iri) {
4756
+ const prefix = iri.split(":")[0];
4757
+ const ns = prefixCC[prefix] ?? qaecyPrefixes[prefix];
4758
+ return iri.replace(`${prefix}:`, ns);
4759
+ }
4760
+ _buildNSMap() {
4761
+ const map = {};
4762
+ Object.keys(prefixCC).forEach((key) => map[prefixCC[key]] = key);
4763
+ Object.keys(qaecyPrefixes).forEach(
4764
+ (key) => map[qaecyPrefixes[key]] = key
4765
+ );
4766
+ this._nsMap = map;
4767
+ return map;
4768
+ }
4769
+ };
4770
+
4724
4771
  // libs/js/sync-tools/src/lib/list-remote-files.ts
4725
4772
  async function listRemoteFiles(spaceId, providerId, queryHandler2, verbose = false) {
4726
4773
  const firebase = CueFirebase.getInstance();
@@ -5097,8 +5144,10 @@ var ENDPOINT_CREATE_PROJECT = "/commands/admin/project";
5097
5144
  var ENDPOINT_SEARCH = "/assistant/search";
5098
5145
  var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
5099
5146
  var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
5147
+ var ENDPOINT_FUSEKI_SHACL = "/triplestore/shacl";
5100
5148
  var ENDPOINT_QLEVER_QUERY = "/qlever-server/qlever/query";
5101
5149
  var ENDPOINT_QLEVER_UPDATE = "/qlever-server/qlever/update";
5150
+ var ENDPOINT_QLEVER_SHACL = "/qlever-server/qlever/shacl";
5102
5151
  var ENDPOINT_FSS_BATCH = "/commands/file-system-structure/batch";
5103
5152
  var MICROSOFT_PROVIDER_ID = "microsoft.com";
5104
5153
  var SUPERADMIN_ROLE = "superadmin";
@@ -5290,6 +5339,49 @@ var CueAuth = class {
5290
5339
  }
5291
5340
  };
5292
5341
 
5342
+ // libs/js/cue-sdk/src/lib/tables.ts
5343
+ var ENDPOINT_DATA_VIEWS_TABLES = "/data-views/tables";
5344
+ var ENDPOINT_COMMANDS_TABLES = "/commands/tables";
5345
+ var CueTables = class {
5346
+ constructor(_auth, _gatewayUrl) {
5347
+ this._auth = _auth;
5348
+ this._gatewayUrl = _gatewayUrl;
5349
+ }
5350
+ async listTables(projectId) {
5351
+ const response = await this._auth.authenticatedFetch(
5352
+ `${this._gatewayUrl}${ENDPOINT_DATA_VIEWS_TABLES}`,
5353
+ {
5354
+ headers: {
5355
+ "Content-Type": "application/json",
5356
+ "x-project-id": projectId,
5357
+ "cue-project-id": projectId
5358
+ }
5359
+ }
5360
+ );
5361
+ if (!response.ok) {
5362
+ throw new Error(`Failed to list tables: ${response.status} ${response.statusText}`);
5363
+ }
5364
+ return response.json();
5365
+ }
5366
+ async saveTables(tables, projectId) {
5367
+ const response = await this._auth.authenticatedFetch(
5368
+ `${this._gatewayUrl}${ENDPOINT_COMMANDS_TABLES}`,
5369
+ {
5370
+ method: "PUT",
5371
+ headers: {
5372
+ "Content-Type": "application/json",
5373
+ "x-project-id": projectId,
5374
+ "cue-project-id": projectId
5375
+ },
5376
+ body: JSON.stringify(tables)
5377
+ }
5378
+ );
5379
+ if (!response.ok) {
5380
+ throw new Error(`Failed to save tables: ${response.status} ${response.statusText}`);
5381
+ }
5382
+ }
5383
+ };
5384
+
5293
5385
  // libs/js/cue-sdk/src/lib/api.ts
5294
5386
  var CueApi = class {
5295
5387
  constructor(_auth, _gatewayUrl, projects, sync) {
@@ -5297,6 +5389,14 @@ var CueApi = class {
5297
5389
  this._gatewayUrl = _gatewayUrl;
5298
5390
  this.projects = projects;
5299
5391
  this.sync = sync;
5392
+ this.tables = new CueTables(_auth, _gatewayUrl);
5393
+ }
5394
+ tables;
5395
+ /** Active language used for language-sensitive SPARQL queries across all project classes. */
5396
+ language = "en";
5397
+ /** Updates the active language. All project classes (`CueProjectSchema`, `CueProjectDocuments`, `CueProjectEntities`) read this at query time. */
5398
+ setLanguage(lang) {
5399
+ this.language = lang;
5300
5400
  }
5301
5401
  /**
5302
5402
  * Returns standard authentication headers for the current user.
@@ -5368,6 +5468,57 @@ var CueApi = class {
5368
5468
  }
5369
5469
  return response.json();
5370
5470
  }
5471
+ /**
5472
+ * Validate a SHACL shape against the project's triplestore.
5473
+ *
5474
+ * @param shape - SHACL shapes graph in Turtle syntax.
5475
+ * @param projectId - Project to validate against.
5476
+ * @param options - `format`: `'json-ld'` (default, structured result) or `'turtle'` (raw string).
5477
+ * `verbose`: include server-side timing logs.
5478
+ *
5479
+ * Returns a {@link ShaclValidationReport} for JSON-LD, or a Turtle string.
5480
+ */
5481
+ async shacl(shape, projectId, options) {
5482
+ const fmt = options?.format ?? "json-ld";
5483
+ const accept = fmt === "turtle" ? "text/turtle" : "application/ld+json";
5484
+ if (options?.graphType === "fuseki") {
5485
+ const response2 = await this._auth.authenticatedFetch(
5486
+ `${this._gatewayUrl}${ENDPOINT_FUSEKI_SHACL}`,
5487
+ {
5488
+ method: "POST",
5489
+ headers: {
5490
+ "Content-Type": "text/turtle",
5491
+ "Accept": accept,
5492
+ "x-project-id": projectId,
5493
+ "cue-project-id": projectId
5494
+ },
5495
+ body: shape
5496
+ }
5497
+ );
5498
+ if (!response2.ok) {
5499
+ const msg = await response2.text().catch(() => "");
5500
+ throw new Error(`SHACL validation failed: ${response2.status} ${response2.statusText}${msg ? " \u2014 " + msg.slice(0, 200) : ""}`);
5501
+ }
5502
+ return fmt === "turtle" ? response2.text() : response2.json();
5503
+ }
5504
+ const url = `${this._gatewayUrl}${ENDPOINT_QLEVER_SHACL}${options?.verbose ? "?verbose=true" : ""}`;
5505
+ const body = new URLSearchParams({ shape });
5506
+ const response = await this._auth.authenticatedFetch(url, {
5507
+ method: "POST",
5508
+ headers: {
5509
+ "Content-Type": "application/x-www-form-urlencoded",
5510
+ "Accept": accept,
5511
+ "x-project-id": projectId,
5512
+ "cue-project-id": projectId
5513
+ },
5514
+ body
5515
+ });
5516
+ if (!response.ok) {
5517
+ const msg = await response.text().catch(() => "");
5518
+ throw new Error(`SHACL validation failed: ${response.status} ${response.statusText}${msg ? " \u2014 " + msg.slice(0, 200) : ""}`);
5519
+ }
5520
+ return fmt === "turtle" ? response.text() : response.json();
5521
+ }
5371
5522
  async getConsumption(projectId) {
5372
5523
  const response = await this._auth.authenticatedFetch(
5373
5524
  `${this._gatewayUrl}${ENDPOINT_CONSUMPTION}`,
@@ -7552,19 +7703,20 @@ var CueProjectSchema = class {
7552
7703
  this._projectId = _projectId;
7553
7704
  this._queryCache = _queryCache;
7554
7705
  this._graphType = _graphType;
7555
- this._language = new CueSignal(language);
7706
+ this._currentLang = language;
7707
+ this._api.setLanguage(language);
7556
7708
  this._contentCategories = new CueSignal([]);
7557
7709
  this._entityCategories = new CueSignal([]);
7558
7710
  this._relationships = new CueSignal([]);
7559
7711
  this.availableContentCategories = this._contentCategories.asReadonly();
7560
7712
  this.availableEntityCategories = this._entityCategories.asReadonly();
7561
7713
  this.availableEntityRelationships = this._relationships.asReadonly();
7562
- this._load(language).catch(
7714
+ this.ready = this._load(language).catch(
7563
7715
  (err) => console.error("[CueProjectSchema] Initial load failed:", err)
7564
7716
  );
7565
7717
  }
7566
7718
  _cache = /* @__PURE__ */ new Map();
7567
- _language;
7719
+ _currentLang;
7568
7720
  _contentCategories;
7569
7721
  _entityCategories;
7570
7722
  _relationships;
@@ -7574,9 +7726,14 @@ var CueProjectSchema = class {
7574
7726
  availableEntityCategories;
7575
7727
  /** Currently active entity relationship types for the selected language. */
7576
7728
  availableEntityRelationships;
7729
+ /**
7730
+ * Resolves when the initial schema load for the constructor language has
7731
+ * completed (or failed). Await this before reading signal values imperatively.
7732
+ */
7733
+ ready;
7577
7734
  /** Returns the currently active language. */
7578
7735
  get language() {
7579
- return this._language.get();
7736
+ return this._api.language;
7580
7737
  }
7581
7738
  /**
7582
7739
  * Switch the active language. If the data for this language has already been
@@ -7584,9 +7741,10 @@ var CueProjectSchema = class {
7584
7741
  * is triggered.
7585
7742
  */
7586
7743
  setLanguage(lang) {
7587
- if (this._language.get() === lang)
7744
+ if (this._currentLang === lang)
7588
7745
  return;
7589
- this._language.set(lang);
7746
+ this._currentLang = lang;
7747
+ this._api.setLanguage(lang);
7590
7748
  this._load(lang).catch(
7591
7749
  (err) => console.error("[CueProjectSchema] Language switch failed:", err)
7592
7750
  );
@@ -7596,7 +7754,7 @@ var CueProjectSchema = class {
7596
7754
  * Useful when the triplestore data has changed.
7597
7755
  */
7598
7756
  async refresh() {
7599
- const lang = this._language.get();
7757
+ const lang = this._api.language;
7600
7758
  this._cache.delete(lang);
7601
7759
  await this._load(lang);
7602
7760
  }
@@ -7622,7 +7780,7 @@ var CueProjectSchema = class {
7622
7780
  },
7623
7781
  (snapshot) => {
7624
7782
  this._cache.set(lang, snapshot);
7625
- if (this._language.get() === lang)
7783
+ if (this._currentLang === lang)
7626
7784
  this._apply(snapshot);
7627
7785
  },
7628
7786
  this._queryCache
@@ -7711,7 +7869,9 @@ var CueProjectEntities = class {
7711
7869
  baseURL;
7712
7870
  // ── Internal writable slices ───────────────────────────────────────────────
7713
7871
  _entityDetails = new CueSignal({});
7714
- _entityDocuments = new CueSignal({});
7872
+ _entityDocuments = new CueSignal(
7873
+ {}
7874
+ );
7715
7875
  _entityRelationships = new CueSignal({});
7716
7876
  _entityOSMMap = new CueSignal({});
7717
7877
  _osmWKTMap = new CueSignal({});
@@ -7753,7 +7913,10 @@ var CueProjectEntities = class {
7753
7913
  this._entityGraph.set(void 0);
7754
7914
  this._fetchingOSMIds.clear();
7755
7915
  this._fetchEntityGraph().catch(
7756
- (err) => console.error("[CueProjectEntities] Entity graph fetch failed after reset:", err)
7916
+ (err) => console.error(
7917
+ "[CueProjectEntities] Entity graph fetch failed after reset:",
7918
+ err
7919
+ )
7757
7920
  );
7758
7921
  }
7759
7922
  // ── Public imperative API ──────────────────────────────────────────────────
@@ -7764,7 +7927,9 @@ var CueProjectEntities = class {
7764
7927
  * Data is merged into `entityInfoMap` once the SPARQL response arrives.
7765
7928
  */
7766
7929
  requestEntityData(uuids, includeMentionCount = false) {
7767
- const newUUIDs = uuids.filter((id) => this._entityDetails.get()[id] === void 0);
7930
+ const newUUIDs = uuids.filter(
7931
+ (id) => this._entityDetails.get()[id] === void 0
7932
+ );
7768
7933
  if (newUUIDs.length === 0)
7769
7934
  return;
7770
7935
  const values = newUUIDs.map((id) => `r:${id}`).join(" ");
@@ -7782,7 +7947,9 @@ WHERE {
7782
7947
  GROUP BY ?id ?mentionCount`;
7783
7948
  this._api.sparql(q, this._projectId, this._graphType).then((data) => {
7784
7949
  const result = data;
7785
- const updates = { ...this._entityDetails.get() };
7950
+ const updates = {
7951
+ ...this._entityDetails.get()
7952
+ };
7786
7953
  result.results.bindings.forEach((b) => {
7787
7954
  if (!b["id"])
7788
7955
  return;
@@ -7806,7 +7973,9 @@ GROUP BY ?id ?mentionCount`;
7806
7973
  * query and merged into `entityInfoMap` reactively once it arrives.
7807
7974
  */
7808
7975
  async requestEntityLocations(uuids) {
7809
- const newUUIDs = uuids.filter((id) => this._entityOSMMap.get()[id] === void 0);
7976
+ const newUUIDs = uuids.filter(
7977
+ (id) => this._entityOSMMap.get()[id] === void 0
7978
+ );
7810
7979
  if (newUUIDs.length === 0)
7811
7980
  return;
7812
7981
  const osmInit = { ...this._entityOSMMap.get() };
@@ -7842,7 +8011,11 @@ WHERE {
7842
8011
  }
7843
8012
  BIND(REPLACE(STR(?iri), "^.*/([^/]*)$", "$1") AS ?id)
7844
8013
  }`;
7845
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8014
+ const data = await this._api.sparql(
8015
+ q,
8016
+ this._projectId,
8017
+ this._graphType
8018
+ );
7846
8019
  const update = { ...this._entityOSMMap.get() };
7847
8020
  data.results.bindings.forEach((b) => {
7848
8021
  if (!b["id"] || !b["osm"])
@@ -7881,7 +8054,10 @@ WHERE {
7881
8054
  this._fetchIncomingRelationships(iri)
7882
8055
  ]);
7883
8056
  const result = { outgoing, incoming };
7884
- this._entityRelationships.set({ ...this._entityRelationships.get(), [uuid]: result });
8057
+ this._entityRelationships.set({
8058
+ ...this._entityRelationships.get(),
8059
+ [uuid]: result
8060
+ });
7885
8061
  return result;
7886
8062
  }
7887
8063
  /**
@@ -7905,11 +8081,111 @@ WHERE {
7905
8081
  qcy:about ?iri .
7906
8082
  BIND(REPLACE(STR(?doc), "^.*/([^/]*)$", "$1") AS ?id)
7907
8083
  }`;
7908
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8084
+ const data = await this._api.sparql(
8085
+ q,
8086
+ this._projectId,
8087
+ this._graphType
8088
+ );
7909
8089
  const ids = data.results.bindings.filter((b) => b["id"] !== void 0).map((b) => b["id"].value);
7910
8090
  this._entityDocuments.set({ ...this._entityDocuments.get(), [uuid]: ids });
7911
8091
  return ids;
7912
8092
  }
8093
+ /**
8094
+ * Fetches all `qcy:EntityCategory` IRIs and their preferred labels for this
8095
+ * project. Uses `api.language` (default `'en'`);
8096
+ * falls back to an untagged label when no match is found.
8097
+ */
8098
+ async contentCategoriesInProject(orderByOccurences = true) {
8099
+ const language = this._api.language;
8100
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8101
+ PREFIX skos: <${prefixCC["skos"]}>
8102
+ SELECT ?iri (SAMPLE(?l) AS ?label) ?count
8103
+ WHERE {
8104
+ ?iri a qcy:EntityCategory .
8105
+ OPTIONAL { ?iri skos:prefLabel ?lang_label FILTER(LANG(?lang_label) = "${language}") }
8106
+ OPTIONAL { ?iri skos:prefLabel ?no_lang_label }
8107
+ BIND(COALESCE(?lang_label, ?no_lang_label) AS ?l)
8108
+ ${orderByOccurences ? "{ SELECT (COUNT(?x) AS ?count) ?iri where { ?x a qcy:CanonicalEntity ; qcy:hasEntityCategory ?iri } GROUP BY ?iri }" : ""}
8109
+ }
8110
+ GROUP BY ?iri ?count
8111
+ ORDER BY ${orderByOccurences ? "DESC(?count)" : "ASC(?label)"}`;
8112
+ const data = await this._api.sparql(
8113
+ q,
8114
+ this._projectId,
8115
+ this._graphType
8116
+ );
8117
+ return data.results.bindings.filter((b) => b["iri"] !== void 0).map((b) => {
8118
+ const count = b["count"] ? parseInt(b["count"].value, 10) : 0;
8119
+ const label = b["label"]?.value ?? b["iri"].value.split("#").at(-1);
8120
+ const labelWithCount = count > 0 ? `${label} (${count})` : label ?? "";
8121
+ return {
8122
+ iri: b["iri"].value,
8123
+ label: labelWithCount
8124
+ };
8125
+ });
8126
+ }
8127
+ async buildSummaryGraph(format) {
8128
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8129
+ SELECT
8130
+ ?sourceCat
8131
+ ?predicate
8132
+ ?targetCat
8133
+ (COUNT(*) AS ?weight)
8134
+ WHERE {
8135
+ ?s a qcy:CanonicalEntity ;
8136
+ qcy:hasEntityCategory ?sourceCat .
8137
+ ?o a qcy:CanonicalEntity ;
8138
+ qcy:hasEntityCategory ?targetCat .
8139
+ ?s ?predicate ?o .
8140
+ FILTER(isIRI(?s) && isIRI(?o))
8141
+ FILTER(?predicate != qcy:relatedEntity)
8142
+ }
8143
+ GROUP BY ?sourceCat ?predicate ?targetCat
8144
+ ORDER BY DESC(?weight)`;
8145
+ const data = await this._api.sparql(
8146
+ q,
8147
+ this._projectId,
8148
+ this._graphType
8149
+ );
8150
+ const bindings = data.results.bindings.filter(
8151
+ (b) => b["sourceCat"] && b["predicate"] && b["targetCat"]
8152
+ );
8153
+ if (format === "graph") {
8154
+ const irisSet = /* @__PURE__ */ new Set();
8155
+ const relations = [];
8156
+ for (const b of bindings) {
8157
+ const sourceID = b["sourceCat"].value;
8158
+ const predicate = b["predicate"].value;
8159
+ const targetID = b["targetCat"].value;
8160
+ const weight = parseInt(b["weight"].value, 10);
8161
+ irisSet.add(sourceID);
8162
+ irisSet.add(targetID);
8163
+ relations.push({ sourceID, predicate, targetID, weight });
8164
+ }
8165
+ return {
8166
+ entities: Array.from(irisSet).map((iri) => ({ iri })),
8167
+ relations
8168
+ };
8169
+ }
8170
+ if (format === "md") {
8171
+ const ce = CompactExpand.getInstance();
8172
+ const rows = bindings.map((b) => ({
8173
+ src: ce.compactIRI(b["sourceCat"].value),
8174
+ pred: ce.compactIRI(b["predicate"].value),
8175
+ tgt: ce.compactIRI(b["targetCat"].value),
8176
+ weight: parseInt(b["weight"].value, 10)
8177
+ }));
8178
+ if (rows.length === 0)
8179
+ return "(no results)";
8180
+ const maxSrc = Math.max(...rows.map((r) => r.src.length));
8181
+ const maxPred = Math.max(...rows.map((r) => r.pred.length));
8182
+ const maxTgt = Math.max(...rows.map((r) => r.tgt.length));
8183
+ return rows.map(
8184
+ (r) => `${r.src.padEnd(maxSrc)} -> ${r.pred.padEnd(maxPred)} -> ${r.tgt.padEnd(maxTgt)} (${r.weight})`
8185
+ ).join("\n");
8186
+ }
8187
+ return data;
8188
+ }
7913
8189
  // ── Private helpers ────────────────────────────────────────────────────────
7914
8190
  _computeEntityInfoMap() {
7915
8191
  const details = this._entityDetails.get();
@@ -7983,7 +8259,11 @@ WHERE {
7983
8259
  FILTER(?rel != qcy:relatedEntity)
7984
8260
  }
7985
8261
  GROUP BY ?rel ?related`;
7986
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8262
+ const data = await this._api.sparql(
8263
+ q,
8264
+ this._projectId,
8265
+ this._graphType
8266
+ );
7987
8267
  const result = [];
7988
8268
  const detailsUpdate = { ...this._entityDetails.get() };
7989
8269
  data.results.bindings.forEach((b) => {
@@ -8017,7 +8297,11 @@ WHERE {
8017
8297
  FILTER(?rel != qcy:relatedEntity)
8018
8298
  }
8019
8299
  GROUP BY ?rel ?relating`;
8020
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8300
+ const data = await this._api.sparql(
8301
+ q,
8302
+ this._projectId,
8303
+ this._graphType
8304
+ );
8021
8305
  const result = [];
8022
8306
  const detailsUpdate = { ...this._entityDetails.get() };
8023
8307
  data.results.bindings.forEach((b) => {
@@ -8052,7 +8336,11 @@ GROUP BY ?e1Cat ?e2Cat`;
8052
8336
  await staleWhileRevalidate(
8053
8337
  q,
8054
8338
  async () => {
8055
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8339
+ const data = await this._api.sparql(
8340
+ q,
8341
+ this._projectId,
8342
+ this._graphType
8343
+ );
8056
8344
  const entities = [];
8057
8345
  const relations = [];
8058
8346
  data.results.bindings.forEach((b) => {
@@ -8109,7 +8397,11 @@ SELECT * WHERE {
8109
8397
  ?s geo:hasGeometry/geo:asWKT ?wkt
8110
8398
  }
8111
8399
  }`;
8112
- const data = await this._api.sparql(q, this._projectId, this._graphType);
8400
+ const data = await this._api.sparql(
8401
+ q,
8402
+ this._projectId,
8403
+ this._graphType
8404
+ );
8113
8405
  const update = { ...this._osmWKTMap.get() };
8114
8406
  data.results.bindings.forEach((b) => {
8115
8407
  if (!b["s"] || !b["wkt"])
@@ -8129,13 +8421,14 @@ var CueProjectDocuments = class {
8129
8421
  this._queryCache = _queryCache;
8130
8422
  this._graphType = _graphType;
8131
8423
  this.baseURL = `${rdfBase}${_projectId}/`;
8132
- this._language = language;
8424
+ this._currentLang = language ?? this._api.language;
8133
8425
  this.documentInfoMap = this._documentInfoMap.asReadonly();
8134
8426
  this.projectDocumentsData = this._projectDocumentsData.asReadonly();
8135
8427
  }
8136
8428
  /** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */
8137
8429
  baseURL;
8138
- _language;
8430
+ /** Tracks the language for which `_documentInfoMap` is currently populated. */
8431
+ _currentLang;
8139
8432
  _documentInfoMap = new CueSignal({});
8140
8433
  _projectDocumentsData = new CueSignal({
8141
8434
  duplicateCount: 0,
@@ -8165,9 +8458,10 @@ var CueProjectDocuments = class {
8165
8458
  * `requestDocumentData()` call.
8166
8459
  */
8167
8460
  setLanguage(lang) {
8168
- if (this._language === lang)
8461
+ if (this._currentLang === lang)
8169
8462
  return;
8170
- this._language = lang;
8463
+ this._currentLang = lang;
8464
+ this._api.setLanguage(lang);
8171
8465
  this._documentInfoMap.set({});
8172
8466
  }
8173
8467
  // ── Public API ─────────────────────────────────────────────────────────────
@@ -8209,7 +8503,7 @@ var CueProjectDocuments = class {
8209
8503
  if (newUUIDs.length === 0)
8210
8504
  return;
8211
8505
  const values = newUUIDs.map((id) => `r:${id}`).join(" ");
8212
- const lang = this._language;
8506
+ const lang = this._api.language;
8213
8507
  const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8214
8508
  PREFIX r: <${this.baseURL}>
8215
8509
  SELECT ?id ?contentIRI ?suffix ?size ?subject ?summary
@@ -8262,6 +8556,21 @@ GROUP BY ?id ?contentIRI ?suffix ?size ?subject ?summary`;
8262
8556
  (err) => console.error("[CueProjectDocuments] requestDocumentData failed:", err)
8263
8557
  );
8264
8558
  }
8559
+ /**
8560
+ * Returns a single arbitrary file path from the project's triplestore.
8561
+ * Useful for pre-filling path-based query inputs with a realistic example.
8562
+ */
8563
+ async randomFilePath() {
8564
+ const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
8565
+ SELECT ?path
8566
+ WHERE {
8567
+ ?fl a qcy:FileLocation ;
8568
+ qcy:filePath ?path .
8569
+ }
8570
+ LIMIT 1`;
8571
+ const data = await this._api.sparql(q, this._projectId, this._graphType);
8572
+ return data.results.bindings[0]?.["path"]?.value ?? null;
8573
+ }
8265
8574
  // ── Private helpers ────────────────────────────────────────────────────────
8266
8575
  async _fetchDocumentsBySuffix() {
8267
8576
  return this._runDocumentsBySuffixQuery(this._buildDocumentsBySuffixQuery());
@@ -8364,6 +8673,7 @@ var CueProjectView = class {
8364
8673
  this.availableContentCategories = this.schema.availableContentCategories;
8365
8674
  this.availableEntityCategories = this.schema.availableEntityCategories;
8366
8675
  this.availableEntityRelationships = this.schema.availableEntityRelationships;
8676
+ this.schemaReady = this.schema.ready;
8367
8677
  this.entityInfoMap = this.entities.entityInfoMap;
8368
8678
  this.entityGraph = this.entities.entityGraph;
8369
8679
  this.documentInfoMap = this.documents.documentInfoMap;
@@ -8384,6 +8694,11 @@ var CueProjectView = class {
8384
8694
  availableEntityCategories;
8385
8695
  /** Available entity relationship types. Auto-fetched on init. */
8386
8696
  availableEntityRelationships;
8697
+ /**
8698
+ * Resolves when the initial schema load has completed. Await before reading
8699
+ * schema signal values imperatively.
8700
+ */
8701
+ schemaReady;
8387
8702
  /** Merged per-entity detail map. Populated lazily via `requestEntityData()` etc. */
8388
8703
  entityInfoMap;
8389
8704
  /** Project-level entity co-occurrence graph. Fetched once on init. */
@@ -11201,10 +11516,17 @@ async function syncHandler(options) {
11201
11516
  process.exit(0);
11202
11517
  } catch (err) {
11203
11518
  const msg = err instanceof Error ? err.message : String(err);
11519
+ const cause = err instanceof Error ? err.cause : void 0;
11520
+ const causeCode = cause instanceof Error ? cause.code : void 0;
11521
+ const causeMsg = cause instanceof Error ? cause.message : void 0;
11522
+ const isTimeout = causeCode === "UND_ERR_HEADERS_TIMEOUT" || causeCode === "UND_ERR_BODY_TIMEOUT" || causeCode === "UND_ERR_CONNECT_TIMEOUT" || causeMsg?.includes("Timeout") || msg.includes("fetch failed");
11204
11523
  if (msg.includes("GRAPH_UNAVAILABLE")) {
11205
11524
  console.error("The knowledge graph for this project has no database configured. Contact your administrator or check the project setup.");
11206
- } else if (msg.includes("GRAPH_TIMEOUT")) {
11207
- console.error("Could not reach the knowledge graph. Make sure all required services are running.");
11525
+ } else if (msg.includes("GRAPH_TIMEOUT") || isTimeout) {
11526
+ console.error("Could not reach the QAECY API \u2014 the request timed out or the server could not be reached.");
11527
+ console.error("Check your internet connection and try again. If the problem persists, the service may be temporarily unavailable.");
11528
+ if (verbose && causeCode)
11529
+ console.error(` Cause: ${causeMsg ?? causeCode}`);
11208
11530
  } else if (msg.includes("METADATA_SYNC_FAILED")) {
11209
11531
  console.error("Metadata sync failed. Try again.");
11210
11532
  } else if (msg.includes("LEDGER_WRITE_FAILED") || msg.includes("File structure batch POST failed")) {
@@ -11212,7 +11534,13 @@ async function syncHandler(options) {
11212
11534
  "Failed communication with the knowledge graph. The files are up to date but metadata is not. Run the tool again to sync metadata. This is a swift job."
11213
11535
  );
11214
11536
  } else {
11215
- console.error("[syncHandler] Unexpected error:", err);
11537
+ console.error("An unexpected error occurred during sync.");
11538
+ if (verbose) {
11539
+ console.error("Details:", err);
11540
+ } else {
11541
+ console.error(` ${msg}`);
11542
+ console.error("Re-run with --verbose for more details.");
11543
+ }
11216
11544
  }
11217
11545
  process.exit(1);
11218
11546
  }
@@ -11675,6 +12003,97 @@ Project created \u2705`);
11675
12003
  }
11676
12004
  }
11677
12005
 
12006
+ // apps/desktop/cue-cli/src/cue-cli-app-builder-tools.ts
12007
+ var import_commander = require("commander");
12008
+ async function buildCueClient(options) {
12009
+ const key = options.key ?? process.env["CUE_API_KEY"];
12010
+ if (!key) {
12011
+ console.error("API key is required. Provide it via --key or CUE_API_KEY env variable.");
12012
+ process.exit(1);
12013
+ }
12014
+ const cue = new CueNode({
12015
+ apiKey: FIREBASE_CONFIG().apiKey,
12016
+ appId: FIREBASE_CONFIG().appId,
12017
+ measurementId: FIREBASE_CONFIG().measurementId,
12018
+ environment: options.emulators ? "emulator" : "production",
12019
+ ...options.emulators ? { endpoints: getEmulatorEndpoints() } : {}
12020
+ });
12021
+ await cue.auth.signInWithApiKey(key);
12022
+ return cue;
12023
+ }
12024
+ async function listProjectsHandler(options) {
12025
+ try {
12026
+ const cue = await buildCueClient(options);
12027
+ if (options.verbose)
12028
+ console.error("Fetching projects...");
12029
+ const projects = await cue.api.projects.listProjects();
12030
+ const output = projects.map((p) => ({
12031
+ id: p.id,
12032
+ name: p.name,
12033
+ organizationID: p.organizationID,
12034
+ isPublic: p.isPublic,
12035
+ graphType: p.projectSettings?.graph?.type ?? "qlever",
12036
+ lastSync: p.lastSync
12037
+ }));
12038
+ console.log(JSON.stringify(output, null, 2));
12039
+ } catch (err) {
12040
+ console.error("Error:", err instanceof Error ? err.message : String(err));
12041
+ process.exit(1);
12042
+ }
12043
+ }
12044
+ async function entitySummaryGraphHandler(options) {
12045
+ try {
12046
+ const cue = await buildCueClient(options);
12047
+ if (options.verbose)
12048
+ console.error(`Fetching entity summary graph for project ${options.space}...`);
12049
+ const entities = new CueProjectEntities(cue.api, options.space);
12050
+ if (options.format === "graph") {
12051
+ const graph = await entities.buildSummaryGraph("graph");
12052
+ console.log(JSON.stringify(graph, null, 2));
12053
+ } else if (options.format === "md") {
12054
+ const md = await entities.buildSummaryGraph("md");
12055
+ console.log(md);
12056
+ } else {
12057
+ const raw = await entities.buildSummaryGraph();
12058
+ console.log(JSON.stringify(raw, null, 2));
12059
+ }
12060
+ } catch (err) {
12061
+ console.error("Error:", err instanceof Error ? err.message : String(err));
12062
+ process.exit(1);
12063
+ }
12064
+ }
12065
+ async function sparqlHandler(options) {
12066
+ try {
12067
+ let sparqlQuery = options.query;
12068
+ if (!sparqlQuery) {
12069
+ if (process.stdin.isTTY) {
12070
+ console.error("Provide a SPARQL query via --query or pipe it via stdin.");
12071
+ process.exit(1);
12072
+ }
12073
+ const chunks = [];
12074
+ for await (const chunk of process.stdin)
12075
+ chunks.push(chunk);
12076
+ sparqlQuery = Buffer.concat(chunks).toString("utf8").trim();
12077
+ }
12078
+ if (!sparqlQuery) {
12079
+ console.error("SPARQL query is empty.");
12080
+ process.exit(1);
12081
+ }
12082
+ const cue = await buildCueClient(options);
12083
+ if (options.verbose)
12084
+ console.error(`Executing SPARQL query against project ${options.space}...`);
12085
+ const result = await cue.api.sparql(sparqlQuery, options.space);
12086
+ console.log(JSON.stringify(result, null, 2));
12087
+ } catch (err) {
12088
+ console.error("Error:", err instanceof Error ? err.message : String(err));
12089
+ process.exit(1);
12090
+ }
12091
+ }
12092
+ var appBuilderToolsCommand = new import_commander.Command("app-builder-tools").description("Tools for agent-assisted Cue app development. Outputs JSON for machine consumption.");
12093
+ appBuilderToolsCommand.command("list-projects").description("List all projects accessible to the authenticated user").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-v, --verbose", "Enable verbose output", false).action(listProjectsHandler);
12094
+ appBuilderToolsCommand.command("entity-summary-graph").description("Fetch the entity category relationship summary graph for a project").requiredOption("-s, --space <id>", "Project ID (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-f, --format <format>", "Output format: md (aligned text), graph (JSON nodes+edges), json (raw SPARQL)", "md").option("-v, --verbose", "Enable verbose output", false).action(entitySummaryGraphHandler);
12095
+ appBuilderToolsCommand.command("sparql").description("Execute a SPARQL SELECT query against a project triplestore. Query via --query or stdin.").requiredOption("-s, --space <id>", "Project ID (required)").option("-q, --query <sparql>", "SPARQL query string (or pipe query via stdin)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-v, --verbose", "Enable verbose output", false).action(sparqlHandler);
12096
+
11678
12097
  // apps/desktop/cue-cli/src/main.ts
11679
12098
  var packageJson;
11680
12099
  try {
@@ -11687,7 +12106,7 @@ try {
11687
12106
  console.warn("Could not find package.json, using fallback version");
11688
12107
  }
11689
12108
  }
11690
- var program = new import_commander.Command();
12109
+ var program = new import_commander2.Command();
11691
12110
  program.name("cue-cli").description("Cue Command Line Interface").version(packageJson.version);
11692
12111
  program.command("sync").description("Sync files to Cue").option("-s, --space <id>", "Specify the space ID (omit to pick interactively)").requiredOption("-p, --path <id>", "Specify the folder path (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("--provider <provider ID>", "Specify the provider ID (eg. sharepoint, drive, dropbox) or leave empty for default provider", "").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-z, --zip", 'Include zipped content (will be unzipped to path "<zip_path>_unzipped". Max uncompressed size: 500 MB, max recursion depth: 3)', false).option("--legacy", "Write RDF as BLOBs to the processed bucket instead of patching the graph directly", false).option("--metadata-only", "Push filesystem-structure metadata for all local files without checking credits or remote state", false).action(syncHandler);
11693
12112
  program.command("dump").description("Dump Cue Knowledge Graph data to file\n Examples:\n $ cue-cli dump -s <space_id> -l -v\n $ cue-cli dump -s <space_id> -j -v").requiredOption("-s, --space <id>", "Specify the space ID (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-v, --verbose", "Enable verbose output", false).option("-e, --emulators", "Uses emulators for sync", false).option("-q, --query", "Uses a construct query to get the dump rather than using the /data endpoint", false).option("-j, --jelly", "Downloads a Jelly file rather than the standard Gzipped NQuads format", false).option("-l, --load", "Loads the dumped file into a local triplestore (requires emulators)", false).action(dumpHandler);
@@ -11697,4 +12116,5 @@ program.command("repair-ttl").description("Repair TTL files in the specified spa
11697
12116
  program.command("util-rdf-compare").description("Compare two Turtle (.ttl) files and report semantic differences").requiredOption("--file1 <path>", "Path to the first TTL file").requiredOption("--file2 <path>", "Path to the second TTL file").option("-v, --verbose", "Enable verbose output", false).action(utilRdfCompareHandler);
11698
12117
  program.command("util-remove-rdf-star").description("Remove RDF-star (quoted) triples from an NQuads file. Supports .nq and .nq.gz input/output.").requiredOption("-i, --input <path>", "Input file path (.nq or .nq.gz)").requiredOption("-o, --output <path>", "Output file path (.nq or .nq.gz)").option("-v, --verbose", "Enable verbose output", false).action(utilRemoveRdfStarHandler);
11699
12118
  program.command("create-project").description("Interactively create a new project under an organisation").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Uses emulators", false).option("-v, --verbose", "Enable verbose output", false).action(createProjectHandler);
12119
+ program.addCommand(appBuilderToolsCommand);
11700
12120
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qaecy/cue-cli",
3
- "version": "0.0.46",
3
+ "version": "0.0.47",
4
4
  "description": "Cue CLI for QAECY platform",
5
5
  "main": "main.js",
6
6
  "bin": {
package/readme.md CHANGED
@@ -35,6 +35,42 @@ To sync the current dir to the space with id `<space-id>` under the provider id
35
35
  | `--legacy` | Write RDF metadata as serialised Turtle BLOBs to the processed storage bucket instead of patching the knowledge graph directly. Use this when the ledger and graph services are not yet available for the target environment. | `false` |
36
36
  | `--metadata-only` | Push filesystem-structure metadata for every local file directly to the commands API, without checking what is already on the remote, running a credit estimate, or uploading any file content. Useful for repairing missing graph metadata after a migration or interrupted sync. Can be combined with `--legacy` to write the metadata as BLOBs. | `false` |
37
37
 
38
+ ### app-builder-tools
39
+
40
+ A set of JSON-output commands designed for agent-assisted Cue app development. All sub-commands accept `-k/--key` (or `CUE_API_KEY`), `-e/--emulators`, and `-v/--verbose`.
41
+
42
+ #### list-projects
43
+
44
+ List all projects accessible to the authenticated user.
45
+
46
+ `npx @qaecy/cue-cli app-builder-tools list-projects`
47
+
48
+ Outputs a JSON array with `id`, `name`, `organizationID`, `isPublic`, `graphType`, and `lastSync` for each project.
49
+
50
+ #### entity-summary-graph
51
+
52
+ Fetch the entity category relationship summary graph for a project — useful for understanding the knowledge graph schema before writing queries.
53
+
54
+ `npx @qaecy/cue-cli app-builder-tools entity-summary-graph -s <project-id>`
55
+
56
+ | Option | Description | Default |
57
+ |----------------------|----------------------------------------------------------------------------------------------|---------|
58
+ | `-s, --space <id>` | Project ID (required) | N/A |
59
+ | `-f, --format <fmt>` | Output format: `md` (aligned text table), `graph` (JSON nodes + weighted edges), `json` (raw SPARQL result) | `md` |
60
+
61
+ #### sparql
62
+
63
+ Execute a SPARQL SELECT query against a project triplestore. The query can be passed inline or piped via stdin. Outputs raw SPARQL JSON results.
64
+
65
+ `npx @qaecy/cue-cli app-builder-tools sparql -s <project-id> -q "SELECT * WHERE { ?s ?p ?o } LIMIT 10"`
66
+
67
+ `echo "SELECT * WHERE { ?s ?p ?o } LIMIT 10" | npx @qaecy/cue-cli app-builder-tools sparql -s <project-id>`
68
+
69
+ | Option | Description | Default |
70
+ |---------------------|-----------------------------------------------------------|---------|
71
+ | `-s, --space <id>` | Project ID (required) | N/A |
72
+ | `-q, --query <sparql>` | SPARQL query string (or pipe query via stdin) | N/A |
73
+
38
74
  ### util-remove-rdf-star
39
75
 
40
76
  Remove RDF-star (quoted triple) statements from an NQuads file. Supports both plain `.nq` and gzipped `.nq.gz` files. If the input is gzipped the output will also be gzipped.