@gobi-ai/cli 0.6.12 → 0.6.14

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.
@@ -54,7 +54,7 @@ export async function uploadAttachments(vaultSlug, links, token, options) {
54
54
  console.log(`Uploading [[${link}]]...`);
55
55
  const content = readFileSync(localPath);
56
56
  const queryString = addToSyncfiles ? "?add_to_syncfiles=true" : "";
57
- const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultSlug}/files/${filePath}${queryString}`;
57
+ const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultSlug}/file/${filePath}${queryString}`;
58
58
  const res = await fetch(url, {
59
59
  method: "PUT",
60
60
  headers: {
@@ -100,7 +100,7 @@ export function registerBrainCommand(program) {
100
100
  }
101
101
  const content = readFileSync(filePath, "utf-8");
102
102
  const token = await getValidToken();
103
- const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/files/BRAIN.md`;
103
+ const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/file/BRAIN.md`;
104
104
  const res = await fetch(url, {
105
105
  method: "PUT",
106
106
  headers: {
@@ -125,7 +125,7 @@ export function registerBrainCommand(program) {
125
125
  .action(async () => {
126
126
  const vaultId = getVaultSlug();
127
127
  const token = await getValidToken();
128
- const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/files/BRAIN.md`;
128
+ const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/file/BRAIN.md`;
129
129
  const res = await fetch(url, {
130
130
  method: "DELETE",
131
131
  headers: { Authorization: `Bearer ${token}` },
@@ -232,7 +232,7 @@ function fileUrl(baseUrl, vaultSlug, filePath) {
232
232
  .split("/")
233
233
  .map((s) => encodeURIComponent(s))
234
234
  .join("/");
235
- return `${baseUrl}/api/v1/vaults/${vaultSlug}/files/${encoded}`;
235
+ return `${baseUrl}/api/v1/vaults/${vaultSlug}/file/${encoded}`;
236
236
  }
237
237
  async function webdriveGet(baseUrl, vaultSlug, filePath, token) {
238
238
  const res = await fetch(fileUrl(baseUrl, vaultSlug, filePath), {
@@ -272,6 +272,7 @@ async function webdriveDelete(baseUrl, vaultSlug, filePath, token) {
272
272
  }
273
273
  async function webdriveSync(baseUrl, vaultSlug, body, token) {
274
274
  const url = `${baseUrl}/api/v1/vaults/${vaultSlug}/sync`;
275
+ process.stderr.write(`[gobi-sync] syncfiles: body=${JSON.stringify(body)}\n`);
275
276
  const res = await fetch(url, {
276
277
  method: "POST",
277
278
  headers: {
@@ -342,8 +343,9 @@ function matchesPaths(filePath, paths) {
342
343
  async function performSync(baseUrl, vaultSlug, state, syncfilesChanges, privatefilesChanges, localFiles, opts, token) {
343
344
  const body = {
344
345
  cursor: state.cursor,
345
- syncfilesChanges,
346
- privatefilesChanges,
346
+ // dryRun: don't mutate server-side syncfiles/privatefiles
347
+ syncfilesChanges: opts.dryRun ? { added: [], removed: [] } : syncfilesChanges,
348
+ privatefilesChanges: opts.dryRun ? { added: [], removed: [] } : privatefilesChanges,
347
349
  clientFiles: localFiles,
348
350
  uploadOnly: opts.uploadOnly,
349
351
  downloadOnly: opts.downloadOnly,
@@ -364,51 +366,41 @@ export async function runSync(opts) {
364
366
  console.log("Full sync: ignoring cursor and hash cache.");
365
367
  }
366
368
  const token = opts.authToken ?? (await getValidToken());
367
- // Sync privatefiles with server
368
369
  // Read syncfiles whitelist
370
+ const syncfilesExistsLocally = existsSync(join(gobiDir, "syncfiles"));
369
371
  const { patterns: currPatterns, contentHash: currSyncfilesHash } = readSyncfiles(gobiDir);
370
372
  if (currPatterns.length === 0 && !jsonMode) {
371
373
  console.warn("Warning: No patterns found in .gobi/syncfiles. Nothing will be synced.\n" +
372
374
  "Add gitignore-style patterns to .gobi/syncfiles to select files for sync.");
373
375
  }
374
376
  const isWhitelisted = buildWhitelistMatcher(currPatterns);
375
- // On bootstrap (no prior state), fetch server's current syncfiles so removals
376
- // relative to the server are captured correctly in the delta.
377
377
  let baseSyncPatterns = state.patterns;
378
- if (!opts.dryRun && state.syncfilesHash === null) {
379
- try {
380
- const serverContent = await webdriveGet(baseUrl, vaultSlug, ".gobi/syncfiles", token);
381
- baseSyncPatterns = serverContent
382
- .toString("utf-8")
383
- .split("\n")
384
- .map((l) => l.trim())
385
- .filter((l) => l.length > 0 && !l.startsWith("#"));
386
- process.stderr.write(`[gobi-sync] bootstrap: fetched server syncfiles, patterns=${JSON.stringify(baseSyncPatterns)}\n`);
387
- }
388
- catch {
389
- baseSyncPatterns = []; // server has no syncfiles yet
390
- }
378
+ if (state.syncfilesHash === null) {
379
+ // Bootstrap: use empty base so local patterns are sent as "added" only.
380
+ // This avoids spuriously removing server-only patterns from other devices.
381
+ // The server returns its current syncfilesHash, which then triggers a download
382
+ // to pull any server patterns the client doesn't have yet.
383
+ baseSyncPatterns = [];
384
+ }
385
+ else if (!syncfilesExistsLocally) {
386
+ // File deleted after a prior sync. Produce empty diff to avoid removing patterns
387
+ // from server. The download condition below re-fetches the missing file.
388
+ currPatterns.length = 0;
389
+ currPatterns.push(...state.patterns);
390
+ baseSyncPatterns = [...state.patterns];
391
391
  }
392
392
  const syncfilesChanges = computeSyncfilesChanges(baseSyncPatterns, currPatterns);
393
- // Compute privatefiles delta (supports removals via sync endpoint)
393
+ // Compute privatefiles delta
394
+ const privatefilesExistsLocally = existsSync(join(gobiDir, "privatefiles"));
394
395
  const currPrivatePatterns = readPrivatefiles(gobiDir);
395
- // On bootstrap (no prior state), fetch server's current patterns so we can compute
396
- // correct removals. Without this, patterns the user deleted would stay on the server
397
- // because we'd have no baseline to diff against.
398
396
  let basePrivatePatterns = state.privatePatterns;
399
- if (!opts.dryRun && state.privatefilesHash === null) {
400
- try {
401
- const serverContent = await webdriveGet(baseUrl, vaultSlug, ".gobi/privatefiles", token);
402
- basePrivatePatterns = serverContent
403
- .toString("utf-8")
404
- .split("\n")
405
- .map((l) => l.trim())
406
- .filter((l) => l.length > 0 && !l.startsWith("#"));
407
- process.stderr.write(`[gobi-sync] bootstrap: fetched server privatefiles, patterns=${JSON.stringify(basePrivatePatterns)}\n`);
408
- }
409
- catch {
410
- basePrivatePatterns = []; // server has no privatefiles yet
411
- }
397
+ if (state.privatefilesHash === null) {
398
+ // Bootstrap: same as syncfiles — empty base, rely on hash comparison for download.
399
+ basePrivatePatterns = [];
400
+ }
401
+ else if (!privatefilesExistsLocally) {
402
+ // File deleted after a prior sync. Produce empty diff.
403
+ basePrivatePatterns = [...currPrivatePatterns];
412
404
  }
413
405
  const privatefilesChanges = computeSyncfilesChanges(basePrivatePatterns, currPrivatePatterns);
414
406
  // Walk local files (only whitelisted, non-ignored)
@@ -603,7 +595,7 @@ export async function runSync(opts) {
603
595
  // Download syncfiles from server if the server's hash changed since last sync
604
596
  let effectivePatterns = currPatterns;
605
597
  process.stderr.write(`[gobi-sync] syncfiles: state=${state.syncfilesHash ?? "null"} server=${syncResp.syncfilesHash ?? "null"}\n`);
606
- if (!opts.dryRun && syncResp.syncfilesHash && syncResp.syncfilesHash !== state.syncfilesHash) {
598
+ if (!opts.dryRun && !opts.uploadOnly && syncResp.syncfilesHash && (syncResp.syncfilesHash !== state.syncfilesHash || !syncfilesExistsLocally)) {
607
599
  process.stderr.write(`[gobi-sync] syncfiles hash changed — downloading from server\n`);
608
600
  try {
609
601
  const syncfilesContent = await webdriveGet(baseUrl, vaultSlug, ".gobi/syncfiles", token);
@@ -623,7 +615,7 @@ export async function runSync(opts) {
623
615
  // Download privatefiles from server if the server's hash changed since last sync
624
616
  let effectivePrivatePatterns = currPrivatePatterns;
625
617
  process.stderr.write(`[gobi-sync] privatefiles: state=${state.privatefilesHash ?? "null"} server=${syncResp.privatefilesHash ?? "null"}\n`);
626
- if (!opts.dryRun && syncResp.privatefilesHash && syncResp.privatefilesHash !== state.privatefilesHash) {
618
+ if (!opts.dryRun && !opts.uploadOnly && syncResp.privatefilesHash && (syncResp.privatefilesHash !== state.privatefilesHash || !privatefilesExistsLocally)) {
627
619
  process.stderr.write(`[gobi-sync] privatefiles hash changed — downloading from server\n`);
628
620
  try {
629
621
  const privatefilesContent = await webdriveGet(baseUrl, vaultSlug, ".gobi/privatefiles", token);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobi-ai/cli",
3
- "version": "0.6.12",
3
+ "version": "0.6.14",
4
4
  "description": "CLI client for the Gobi collaborative knowledge platform",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,12 +10,12 @@ description: >-
10
10
  allowed-tools: Bash(gobi:*)
11
11
  metadata:
12
12
  author: gobi-ai
13
- version: "0.6.11"
13
+ version: "0.6.13"
14
14
  ---
15
15
 
16
16
  # gobi-cli
17
17
 
18
- A CLI client for the Gobi collaborative knowledge platform (v0.6.11).
18
+ A CLI client for the Gobi collaborative knowledge platform (v0.6.13).
19
19
 
20
20
  ## Prerequisites
21
21