@qaecy/cue-cli 0.0.33 → 0.0.34

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/main.js +187 -95
  2. package/package.json +1 -1
package/main.js CHANGED
@@ -107,7 +107,6 @@ var QLEVER_UPDATE_ENDPOINT_EMULATOR = process.env.CUE_QLEVER_ENDPOINT ? `${proce
107
107
  var SPARQL_ENDPOINT_EMULATOR = `${EMULATOR_API_GATEWAY_BASE}/triplestore/query`;
108
108
  var SPARQL_ENDPOINT = "https://accessors-api-gateway-ueyeemwf2a-oa.a.run.app/triplestore/query";
109
109
  var HASH_WORKER_PATH = (0, import_path.join)(__dirname, "hash-worker.js");
110
- var PROJECT_ID = "qaecy-mvp-406413.appspot.com";
111
110
  var FIREBASE_CONFIG = (useEmulator = false) => ({
112
111
  apiKey: "AIzaSyCLhz5Wa3ZCERQZVurSt9bqupPeREALFLk",
113
112
  appId: "1:151132927589:web:d11c64cc55bdc0f13ab88c",
@@ -4141,7 +4140,7 @@ var CueAuth = class {
4141
4140
  // libs/js/cue-sdk/src/lib/api.ts
4142
4141
  var ENDPOINT_SEARCH = "/assistant/search";
4143
4142
  var ENDPOINT_SPARQL = "/triplestore/query";
4144
- var ENDPOINT_CONSUMPTION = "/data-views/admin/consumption/units";
4143
+ var ENDPOINT_CONSUMPTION = "/data-views/admin/consumption";
4145
4144
  var CueApi = class {
4146
4145
  constructor(_auth, _gatewayUrl, projects, sync) {
4147
4146
  this._auth = _auth;
@@ -4201,10 +4200,14 @@ var CueApi = class {
4201
4200
  }
4202
4201
  return response.json();
4203
4202
  }
4204
- async getConsumption(projectId) {
4203
+ async getConsumption(projectId, tier) {
4205
4204
  const headers = await this._authHeaders();
4206
4205
  const response = await fetch(`${this._gatewayUrl}${ENDPOINT_CONSUMPTION}`, {
4207
- headers: { ...headers, "x-project-id": projectId }
4206
+ headers: {
4207
+ ...headers,
4208
+ "x-project-id": projectId,
4209
+ ...tier ? { "x-tier": tier } : {}
4210
+ }
4208
4211
  });
4209
4212
  if (!response.ok) {
4210
4213
  throw new Error(`Failed to fetch consumption: ${response.status} ${response.statusText}`);
@@ -4880,6 +4883,30 @@ var CueBlobStorage = class {
4880
4883
  const result = await (0, import_storage3.listAll)(listRef);
4881
4884
  return result.items.map((item) => item.name);
4882
4885
  }
4886
+ /**
4887
+ * Download a file from the public bucket and return its contents as a string.
4888
+ */
4889
+ async downloadPublic(blobName) {
4890
+ const fileRef = (0, import_storage3.ref)(this.options.storagePublic, blobName);
4891
+ const controller = new AbortController();
4892
+ const timeout = setTimeout(() => controller.abort(), 1e4);
4893
+ try {
4894
+ const [url, metadata] = await Promise.all([
4895
+ (0, import_storage3.getDownloadURL)(fileRef),
4896
+ (0, import_storage3.getMetadata)(fileRef)
4897
+ ]);
4898
+ const cacheBustedUrl = `${url}&t=${encodeURIComponent(metadata.updated)}`;
4899
+ const res = await fetch(cacheBustedUrl, { signal: controller.signal });
4900
+ if (!res.ok)
4901
+ throw new Error(`HTTP ${res.status}`);
4902
+ return res.text();
4903
+ } catch (err) {
4904
+ const isTimeout = err instanceof Error && err.name === "AbortError";
4905
+ throw new Error(isTimeout ? `Download timed out: ${blobName}` : err instanceof Error ? err.message : String(err));
4906
+ } finally {
4907
+ clearTimeout(timeout);
4908
+ }
4909
+ }
4883
4910
  };
4884
4911
 
4885
4912
  // libs/js/cue-sdk/src/lib/sync.ts
@@ -5941,6 +5968,29 @@ var CueSyncApi = class {
5941
5968
  _bindApi(api) {
5942
5969
  this._api = api;
5943
5970
  }
5971
+ /**
5972
+ * Flushes any pending metadata items from a previous interrupted sync.
5973
+ * Safe to call even when there is nothing new to upload.
5974
+ */
5975
+ async flushPendingMetadata(spaceId, verbose) {
5976
+ const token = await this._auth.getToken();
5977
+ if (!token)
5978
+ throw new Error("Not authenticated. Call cue.auth.signIn() first.");
5979
+ const existing = await _loadPending(spaceId);
5980
+ if (!existing || existing.items.length === 0)
5981
+ return;
5982
+ console.info(`Trying to upload metadata (${existing.items.length} item(s))...`);
5983
+ if (verbose)
5984
+ console.info(`Flushing ${existing.items.length} pending file location(s) from previous sync \u23F3`);
5985
+ try {
5986
+ this._pendingSpaceId = spaceId;
5987
+ this._pendingItems = [];
5988
+ await this._flushBatch(existing.items, spaceId, token, verbose);
5989
+ console.info("Metadata uploaded \u2705");
5990
+ } catch (err) {
5991
+ throw new Error(`METADATA_SYNC_FAILED: ${err instanceof Error ? err.message : String(err)}`);
5992
+ }
5993
+ }
5944
5994
  /**
5945
5995
  * Returns a preview of what would be synced: cost breakdown for new files only,
5946
5996
  * units required, and units still available. Use this before calling {@link sync}
@@ -5952,33 +6002,56 @@ var CueSyncApi = class {
5952
6002
  if (!token)
5953
6003
  throw new Error("Not authenticated. Call cue.auth.signIn() first.");
5954
6004
  const graph = await this._getOrCreateGraph(spaceId, token);
5955
- const [remoteFiles, consumption] = await Promise.all([
6005
+ const project = await this._projects.getProject(spaceId);
6006
+ const tier = project?.projectSettings?.tier ?? "l";
6007
+ const [remoteFiles, consumption, creditMap, tierNames] = await Promise.all([
5956
6008
  this._listRemoteFiles(graph, spaceId, providerId, verbose),
5957
- this._api?.getConsumption(spaceId) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance"))
6009
+ this._api?.getConsumption(spaceId, tier) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance")),
6010
+ this._fetchUnitCreditMap(verbose),
6011
+ this._fetchTierNames()
5958
6012
  ]);
5959
6013
  const report = await compareLocalRemote(localFiles, remoteFiles);
5960
6014
  const toUpload = report.localNotOnRemote ?? [];
5961
6015
  const costRecords = toUpload.length > 0 ? await this.scanCost(toUpload) : [];
5962
- const unitsToConsume = costRecords.reduce((sum, r) => sum + r.units, 0);
6016
+ let unitsToConsume = 0;
6017
+ let creditsToConsume = 0;
6018
+ for (const r of costRecords) {
6019
+ unitsToConsume += r.units;
6020
+ const tierMap = creditMap[tier];
6021
+ const creditPerUnit = tierMap?.[r.ext] ?? 1;
6022
+ if (verbose && tierMap && !(r.ext in tierMap)) {
6023
+ console.info(` Unknown format: .${r.ext} (using default rate of 1 credit/unit)`);
6024
+ }
6025
+ const credits = r.units * creditPerUnit;
6026
+ creditsToConsume += credits;
6027
+ r.credits = Math.round(credits);
6028
+ }
6029
+ const tierName = tierNames[tier] ?? tier;
5963
6030
  return {
5964
6031
  costRecords,
6032
+ tier,
6033
+ tierName,
5965
6034
  unitsToConsume,
6035
+ creditsToConsume: Math.round(creditsToConsume),
6036
+ creditsAvailable: consumption.creditsAvailable,
5966
6037
  unitsAvailable: consumption.unitsAvailable,
5967
6038
  filesToUpload: toUpload.length,
5968
6039
  totalLocalFiles: localFiles.length
5969
6040
  };
5970
6041
  }
5971
6042
  async sync(localFiles, options) {
5972
- const { spaceId, providerId, userId, verbose } = options;
6043
+ const { spaceId, providerId, userId, verbose, onProgress } = options;
5973
6044
  const token = await this._auth.getToken();
5974
6045
  if (!token)
5975
6046
  throw new Error("Not authenticated. Call cue.auth.signIn() first.");
5976
6047
  const graph = await this._getOrCreateGraph(spaceId, token);
5977
6048
  if (verbose)
5978
6049
  console.info("Listing remote files \u23F3");
6050
+ const project = await this._projects.getProject(spaceId);
6051
+ const tier = project?.projectSettings?.tier ?? "l";
5979
6052
  const [remoteFiles, consumption] = await Promise.all([
5980
6053
  this._listRemoteFiles(graph, spaceId, providerId, verbose),
5981
- this._api?.getConsumption(spaceId) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance"))
6054
+ this._api?.getConsumption(spaceId, tier) ?? Promise.reject(new Error("CueSyncApi is not bound to a CueApi instance"))
5982
6055
  ]);
5983
6056
  const { unitsAvailable } = consumption;
5984
6057
  const report = await compareLocalRemote(localFiles, remoteFiles);
@@ -6033,7 +6106,7 @@ var CueSyncApi = class {
6033
6106
  rdfWritten = true;
6034
6107
  syncCount += 1;
6035
6108
  syncSize += file.size || 0;
6036
- this._logProgress(syncCount, report.totalCount, syncSize, report.totalSize, verbose);
6109
+ this._logProgress(syncCount, report.totalCount, syncSize, report.totalSize, onProgress);
6037
6110
  } catch (err) {
6038
6111
  failedUploads += 1;
6039
6112
  console.error(`[CueSyncApi] Failed to upload file: ${file.fullPath}`);
@@ -6054,17 +6127,19 @@ var CueSyncApi = class {
6054
6127
  rdfWritten = true;
6055
6128
  syncCount += 1;
6056
6129
  syncSize += file.size || 0;
6057
- this._logProgress(syncCount, report.totalCount, syncSize, report.totalSize, verbose);
6130
+ this._logProgress(syncCount, report.totalCount, syncSize, report.totalSize, onProgress);
6058
6131
  }
6059
6132
  await this._drainPending(verbose);
6060
6133
  this._stopFlushTimer();
6134
+ const postSyncConsumption = await (this._api?.getConsumption(spaceId, tier) ?? Promise.resolve({ creditsAvailable: 0 }));
6061
6135
  return {
6062
6136
  syncCount,
6063
6137
  syncSize,
6064
6138
  failedUploads,
6065
6139
  totalCount: report.totalCount,
6066
6140
  totalSize: report.totalSize,
6067
- rdfWritten
6141
+ rdfWritten,
6142
+ creditsAvailable: postSyncConsumption.creditsAvailable
6068
6143
  };
6069
6144
  }
6070
6145
  async _getOrCreateGraph(spaceId, token) {
@@ -6090,9 +6165,15 @@ var CueSyncApi = class {
6090
6165
  async _listRemoteFiles(graph, spaceId, providerId, verbose) {
6091
6166
  if (verbose)
6092
6167
  console.info(`Listing files in raw space: ${spaceId}`);
6168
+ const graphFilesWithTimeout = Promise.race([
6169
+ this._getGraphFiles(graph, providerId),
6170
+ new Promise(
6171
+ (_, reject) => setTimeout(() => reject(new Error("GRAPH_TIMEOUT: Knowledge graph query timed out")), 15e3)
6172
+ )
6173
+ ]);
6093
6174
  const [blobNames, locationMap] = await Promise.all([
6094
6175
  this._blob.listRaw(spaceId),
6095
- this._getGraphFiles(graph, providerId)
6176
+ graphFilesWithTimeout
6096
6177
  ]);
6097
6178
  if (verbose)
6098
6179
  console.info(`Found ${blobNames.length} files in raw store for space: ${spaceId}`);
@@ -6148,9 +6229,15 @@ WHERE {
6148
6229
  this._pendingItems = [];
6149
6230
  const existing = await _loadPending(spaceId);
6150
6231
  if (existing && existing.items.length > 0) {
6232
+ console.info(`Trying to upload metadata from interrupted sync (${existing.items.length} item(s))...`);
6151
6233
  if (verbose)
6152
6234
  console.info(`Flushing ${existing.items.length} pending file location(s) from previous sync \u23F3`);
6153
- await this._flushBatch(existing.items, spaceId, token, verbose);
6235
+ try {
6236
+ await this._flushBatch(existing.items, spaceId, token, verbose);
6237
+ console.info("Metadata uploaded \u2705");
6238
+ } catch (err) {
6239
+ throw new Error(`METADATA_SYNC_FAILED: ${err instanceof Error ? err.message : String(err)}`);
6240
+ }
6154
6241
  }
6155
6242
  const timer = setInterval(() => {
6156
6243
  this._drainPending(verbose).catch(
@@ -6195,15 +6282,26 @@ WHERE {
6195
6282
  }
6196
6283
  }
6197
6284
  async _postFssBatch(items, spaceId, token) {
6198
- const response = await fetch(`${this._gatewayUrl}${ENDPOINT_FSS_BATCH}`, {
6199
- method: "POST",
6200
- headers: {
6201
- Authorization: `Bearer ${token}`,
6202
- "Content-Type": "application/json",
6203
- "x-project-id": spaceId
6204
- },
6205
- body: JSON.stringify({ items })
6206
- });
6285
+ const controller = new AbortController();
6286
+ const timeout = setTimeout(() => controller.abort(), 15e3);
6287
+ let response;
6288
+ try {
6289
+ response = await fetch(`${this._gatewayUrl}${ENDPOINT_FSS_BATCH}`, {
6290
+ method: "POST",
6291
+ headers: {
6292
+ Authorization: `Bearer ${token}`,
6293
+ "Content-Type": "application/json",
6294
+ "x-project-id": spaceId
6295
+ },
6296
+ body: JSON.stringify({ items }),
6297
+ signal: controller.signal
6298
+ });
6299
+ } catch (err) {
6300
+ const isTimeout = err instanceof Error && err.name === "AbortError";
6301
+ throw new Error(`File structure batch POST failed: ${isTimeout ? "request timed out" : err instanceof Error ? err.message : String(err)}`);
6302
+ } finally {
6303
+ clearTimeout(timeout);
6304
+ }
6207
6305
  if (!response.ok) {
6208
6306
  const body = await response.text().catch(() => "");
6209
6307
  throw new Error(`File structure batch POST failed: ${response.status} ${response.statusText}${body ? ` \u2014 ${body}` : ""}`);
@@ -6252,21 +6350,41 @@ WHERE {
6252
6350
  }
6253
6351
  return Array.from(merged.values());
6254
6352
  }
6255
- _logProgress(syncCount, totalCount, syncSize, totalSize, verbose) {
6256
- if (!verbose || totalCount === 0)
6257
- return;
6258
- if (syncCount % Math.ceil(totalCount / 100) !== 0)
6353
+ async _fetchTierNames() {
6354
+ try {
6355
+ const text = await this._blob.downloadPublic("tier-names.json");
6356
+ return JSON.parse(text);
6357
+ } catch {
6358
+ return {};
6359
+ }
6360
+ }
6361
+ async _fetchUnitCreditMap(verbose) {
6362
+ let text;
6363
+ try {
6364
+ text = await this._blob.downloadPublic("unit-credit.json");
6365
+ } catch (err) {
6366
+ throw new Error(`Couldn't fetch credits table: ${err instanceof Error ? err.message : String(err)}`);
6367
+ }
6368
+ if (verbose)
6369
+ console.info(`Price file: ${text.length} bytes`);
6370
+ try {
6371
+ return JSON.parse(text);
6372
+ } catch {
6373
+ throw new Error(`Credits table is not valid JSON (${text.length} bytes)`);
6374
+ }
6375
+ }
6376
+ _logProgress(syncCount, totalCount, syncSize, totalSize, onProgress) {
6377
+ if (!onProgress || totalCount === 0)
6259
6378
  return;
6260
6379
  const pct = Math.floor(syncCount / totalCount * 100);
6261
- console.info(
6262
- `Progress: ${pct}% (${syncCount}/${totalCount} files, ${syncSize}/${totalSize} bytes)`
6263
- );
6380
+ onProgress({ percent: pct, syncCount, totalCount, syncSize, totalSize });
6264
6381
  }
6265
6382
  };
6266
6383
 
6267
6384
  // libs/js/cue-sdk/src/lib/cue-node.ts
6268
6385
  var BUCKET_RAW2 = "spaces_raw_eu_west6";
6269
6386
  var BUCKET_PROCESSED2 = "spaces_processed_eu_west6";
6387
+ var BUCKET_PUBLIC2 = "cue_public_eu_west6";
6270
6388
  var CueNode = class extends Cue {
6271
6389
  constructor(config) {
6272
6390
  super(config);
@@ -6274,13 +6392,15 @@ var CueNode = class extends Cue {
6274
6392
  _buildApi(projects) {
6275
6393
  const storageRaw = (0, import_storage4.getStorage)(this._app, BUCKET_RAW2);
6276
6394
  const storageProcessed = (0, import_storage4.getStorage)(this._app, BUCKET_PROCESSED2);
6395
+ const storagePublic = (0, import_storage4.getStorage)(this._app, BUCKET_PUBLIC2);
6277
6396
  if (this._isEmulator) {
6278
6397
  const storageHost = this._endpoints.storageEmulatorHost;
6279
6398
  const storagePort = this._endpoints.storageEmulatorPort;
6280
6399
  (0, import_storage4.connectStorageEmulator)(storageRaw, storageHost, storagePort);
6281
6400
  (0, import_storage4.connectStorageEmulator)(storageProcessed, storageHost, storagePort);
6401
+ (0, import_storage4.connectStorageEmulator)(storagePublic, storageHost, storagePort);
6282
6402
  }
6283
- const blob = new CueBlobStorage({ storageRaw, storageProcessed });
6403
+ const blob = new CueBlobStorage({ storageRaw, storageProcessed, storagePublic });
6284
6404
  const syncApi = new CueSyncApi(this.auth, projects, blob, this._endpoints.gatewayUrl);
6285
6405
  const api = new CueApi(this.auth, this._endpoints.gatewayUrl, projects, syncApi);
6286
6406
  syncApi._bindApi(api);
@@ -6896,46 +7016,6 @@ async function repairTtlHandler(options) {
6896
7016
  }
6897
7017
  }
6898
7018
 
6899
- // apps/desktop/cue-cli/src/helpers/emit-idle.ts
6900
- var import_pubsub = require("@google-cloud/pubsub");
6901
- async function emitIdle(spaceId, hoursBack, useEmulator) {
6902
- const from = /* @__PURE__ */ new Date();
6903
- from.setHours(from.getHours() - hoursBack);
6904
- const message = {
6905
- space_id: spaceId,
6906
- first_write: from.toISOString(),
6907
- last_write: (/* @__PURE__ */ new Date()).toISOString()
6908
- };
6909
- await publishMessage("RDF_WRITING_IDLE", message, useEmulator);
6910
- }
6911
- async function publishMessage(topicName, data, useEmulator) {
6912
- if (useEmulator) {
6913
- process.env.PUBSUB_EMULATOR_HOST = process.env.PUBSUB_EMULATOR_HOST || "localhost:8085";
6914
- }
6915
- const projectId = useEmulator ? "demo-project" : PROJECT_ID;
6916
- const pubSubClient = new import_pubsub.PubSub({ projectId });
6917
- const topic = pubSubClient.topic(topicName);
6918
- const dataBuffer = Buffer.from(JSON.stringify(data));
6919
- try {
6920
- if (useEmulator) {
6921
- const publishPromise = topic.publishMessage({ data: dataBuffer });
6922
- const timeoutPromise = new Promise(
6923
- (_, reject) => setTimeout(() => reject(new Error("PubSub publish timeout")), 5e3)
6924
- );
6925
- await Promise.race([publishPromise, timeoutPromise]);
6926
- } else {
6927
- await topic.publishMessage({ data: dataBuffer });
6928
- }
6929
- } catch (error) {
6930
- console.error(`Error publishing message: ${error.message}`);
6931
- if (useEmulator) {
6932
- console.warn("Continuing despite PubSub error in emulator mode...");
6933
- } else {
6934
- throw error;
6935
- }
6936
- }
6937
- }
6938
-
6939
7019
  // apps/desktop/cue-cli/src/cue-cli-sync.ts
6940
7020
  var import_promises7 = require("fs/promises");
6941
7021
  var import_fs6 = require("fs");
@@ -7005,7 +7085,14 @@ async function syncHandler(options) {
7005
7085
  console.info("Built sync base \u2705\n");
7006
7086
  if (verbose)
7007
7087
  console.info("Authenticating \u23F3");
7008
- const user = await cue.auth.signInWithApiKey(key, space);
7088
+ let user;
7089
+ try {
7090
+ user = await cue.auth.signInWithApiKey(key, space);
7091
+ } catch (err) {
7092
+ const cause = err instanceof Error ? err.message : String(err);
7093
+ console.error(`Couldn't authenticate. ${cause}`);
7094
+ process.exit(1);
7095
+ }
7009
7096
  if (verbose)
7010
7097
  console.info("Authenticated \u2705\n");
7011
7098
  if (verbose)
@@ -7016,25 +7103,28 @@ async function syncHandler(options) {
7016
7103
  userId: user.uid,
7017
7104
  verbose
7018
7105
  });
7106
+ console.info(`Project is configured at the ${preview.tierName} tier`);
7019
7107
  console.info("Cost estimate (new files only):");
7020
7108
  if (preview.costRecords.length === 0) {
7021
7109
  console.info(" Nothing new to sync.");
7022
7110
  } else {
7023
7111
  for (const r of preview.costRecords) {
7024
- console.info(` .${r.ext}: ${r.count} file(s) \u2192 ${r.units} unit(s)`);
7112
+ const creditsStr = r.credits !== void 0 ? ` (${Math.round(r.credits)} credits)` : "";
7113
+ console.info(` .${r.ext}: ${r.count} file(s)${creditsStr}`);
7025
7114
  }
7026
7115
  }
7027
7116
  console.info(` New files: ${preview.filesToUpload}/${preview.totalLocalFiles}`);
7028
- console.info(` Units required: ${preview.unitsToConsume}`);
7029
- console.info(` Units available: ${preview.unitsAvailable}
7117
+ console.info(` Credits required: ${Math.round(preview.creditsToConsume)}`);
7118
+ console.info(` Credits available: ${Math.round(preview.creditsAvailable)}
7030
7119
  `);
7120
+ await cue.api.sync.flushPendingMetadata(space, verbose);
7031
7121
  if (preview.filesToUpload === 0) {
7032
7122
  console.info("Everything is already synced.");
7033
7123
  process.exit(0);
7034
7124
  }
7035
- if (preview.unitsToConsume > preview.unitsAvailable) {
7125
+ if (preview.creditsToConsume > preview.creditsAvailable) {
7036
7126
  console.error(
7037
- `Insufficient units: ${preview.unitsToConsume} required but only ${preview.unitsAvailable} available.`
7127
+ `Insufficient credits: ${Math.round(preview.creditsToConsume)} required, ${Math.round(preview.creditsAvailable)} available.`
7038
7128
  );
7039
7129
  process.exit(1);
7040
7130
  }
@@ -7047,24 +7137,14 @@ async function syncHandler(options) {
7047
7137
  spaceId: space,
7048
7138
  providerId: provider,
7049
7139
  userId: user.uid,
7050
- verbose
7051
- });
7052
- if (result.rdfWritten && emulators) {
7053
- if (verbose) {
7054
- console.info("");
7055
- console.info(`Throwing RDF_WRITING_IDLE topic (only in emulators) \u23F3`);
7140
+ verbose,
7141
+ onProgress: ({ percent, syncCount, totalCount }) => {
7142
+ const filled = Math.round(percent / 5);
7143
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
7144
+ process.stdout.write(`\r [${bar}] ${percent}% (${syncCount}/${totalCount} files) `);
7056
7145
  }
7057
- try {
7058
- await emitIdle(space, 1, emulators);
7059
- if (verbose)
7060
- console.info(`Threw RDF_WRITING_IDLE topic \u2705`);
7061
- } catch (error) {
7062
- if (verbose)
7063
- console.error(`Error throwing RDF_WRITING_IDLE topic: ${error instanceof Error ? error.message : String(error)}`);
7064
- if (verbose)
7065
- console.warn("Continuing despite PubSub error in emulator mode...");
7066
- }
7067
- }
7146
+ });
7147
+ process.stdout.write("\n");
7068
7148
  const zipDeletePromise = zip && !isFile ? deleteUnzipped(path) : Promise.resolve();
7069
7149
  await zipDeletePromise;
7070
7150
  if (zip && verbose)
@@ -7074,6 +7154,7 @@ async function syncHandler(options) {
7074
7154
  console.info(
7075
7155
  `Synced: ${result.syncCount}/${result.totalCount} files (${fileSizePretty(result.syncSize)}/${fileSizePretty(result.totalSize)})`
7076
7156
  );
7157
+ console.info(`Credits remaining: ${Math.round(result.creditsAvailable)}`);
7077
7158
  console.info(`Sync finished \u{1F680}\u{1F680}\u{1F680}`);
7078
7159
  if (result.failedUploads > 0) {
7079
7160
  console.warn(`Total files failed to upload: ${result.failedUploads}`);
@@ -7081,7 +7162,18 @@ async function syncHandler(options) {
7081
7162
  }
7082
7163
  process.exit(0);
7083
7164
  } catch (err) {
7084
- console.error("[syncHandler] Unexpected error:", err);
7165
+ const msg = err instanceof Error ? err.message : String(err);
7166
+ if (msg.includes("GRAPH_TIMEOUT")) {
7167
+ console.error("Could not reach the knowledge graph. Make sure all required services are running.");
7168
+ } else if (msg.includes("METADATA_SYNC_FAILED")) {
7169
+ console.error("Metadata sync failed. Try again.");
7170
+ } else if (msg.includes("LEDGER_WRITE_FAILED") || msg.includes("File structure batch POST failed")) {
7171
+ console.error(
7172
+ "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."
7173
+ );
7174
+ } else {
7175
+ console.error("[syncHandler] Unexpected error:", err);
7176
+ }
7085
7177
  process.exit(1);
7086
7178
  }
7087
7179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qaecy/cue-cli",
3
- "version": "0.0.33",
3
+ "version": "0.0.34",
4
4
  "description": "Cue CLI for QAECY platform",
5
5
  "main": "main.js",
6
6
  "bin": {