atris 3.15.12 → 3.15.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.
package/commands/pull.js CHANGED
@@ -566,11 +566,35 @@ async function pullBusiness(slug) {
566
566
  // Compute local file hashes
567
567
  const localFiles = localFilesBeforePull;
568
568
 
569
+ function isPullPathInScope(filePath) {
570
+ if (!onlyPrefixes) return true;
571
+ const rel = filePath.replace(/^\//, '');
572
+ return onlyPrefixes.some((pref) => rel.startsWith(pref));
573
+ }
574
+
575
+ function filterFilesToPullScope(filesMap = {}) {
576
+ if (!onlyPrefixes) return filesMap;
577
+ return Object.fromEntries(
578
+ Object.entries(filesMap).filter(([p]) => isPullPathInScope(p))
579
+ );
580
+ }
581
+
582
+ function filterManifestToPullScope(existingManifest) {
583
+ if (!onlyPrefixes || !existingManifest || !existingManifest.files) return existingManifest;
584
+ return {
585
+ ...existingManifest,
586
+ files: filterFilesToPullScope(existingManifest.files),
587
+ };
588
+ }
589
+
569
590
  // If output dir is empty (fresh clone) or --force, treat as first sync — pull everything
570
- const effectiveManifest = (Object.keys(localFiles).length === 0 || force) ? null : manifest;
591
+ const scopedLocalFiles = filterFilesToPullScope(localFiles);
592
+ const scopedRemoteFiles = filterFilesToPullScope(remoteFiles);
593
+ const scopedManifest = filterManifestToPullScope(manifest);
594
+ const effectiveManifest = (Object.keys(scopedLocalFiles).length === 0 || force) ? null : scopedManifest;
571
595
 
572
596
  // Three-way compare
573
- const diff = threeWayCompare(localFiles, remoteFiles, effectiveManifest);
597
+ const diff = threeWayCompare(scopedLocalFiles, scopedRemoteFiles, effectiveManifest);
574
598
 
575
599
  if (dryRun) {
576
600
  console.log('');
package/commands/push.js CHANGED
@@ -95,11 +95,49 @@ function shouldRetrySyncIndividually(result, filesToPush) {
95
95
  return result.status !== 403 && result.status !== 409;
96
96
  }
97
97
 
98
+ function isMassDeletePlan({ deletedPaths = [], filesToPush = [], unchangedCount = 0 } = {}) {
99
+ const deleteCount = deletedPaths.length;
100
+ if (deleteCount === 0) return false;
101
+ const survivingCount = filesToPush.length + unchangedCount;
102
+ if (deleteCount >= 10 && survivingCount === 0) return true;
103
+ if (deleteCount >= 25) return true;
104
+ return deleteCount >= 10 && deleteCount > survivingCount;
105
+ }
106
+
107
+ function parsePushTimeoutSec(argv = process.argv, defaultSec = 120) {
108
+ let raw = null;
109
+ const eqArg = argv.find(a => a.startsWith('--timeout='));
110
+ if (eqArg) raw = eqArg.slice('--timeout='.length);
111
+ else {
112
+ const idx = argv.indexOf('--timeout');
113
+ if (idx !== -1 && argv[idx + 1]) raw = argv[idx + 1];
114
+ }
115
+
116
+ const parsed = raw == null ? defaultSec : Number.parseInt(raw, 10);
117
+ if (!Number.isFinite(parsed)) return defaultSec;
118
+ return Math.max(5, Math.min(300, parsed));
119
+ }
120
+
98
121
  async function pushAtris() {
99
122
  const elapsedMs = startTimer();
100
123
  let slug = process.argv[3];
101
124
  let _coldWake = false;
102
125
 
126
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
127
+ console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete] [--delete-all]');
128
+ console.log('');
129
+ console.log(' Push requires a fresh pull. If cloud has changed since your last pull,');
130
+ console.log(' the push will be blocked until you run `atris pull`. Use --force to override.');
131
+ console.log('');
132
+ console.log(' atris push Push from current folder (auto-detect business)');
133
+ console.log(' atris push pallet Push pallet/ or atris/pallet/');
134
+ console.log(' atris push pallet --only team/nate Only push files in team/nate/');
135
+ console.log(' atris push --force Bypass freshness check (force-push, may overwrite cloud changes)');
136
+ console.log(' atris push --delete Allow small cloud deletes shown by --dry-run');
137
+ console.log(' atris push --delete-all Extra confirmation for mass-delete recovery');
138
+ process.exit(0);
139
+ }
140
+
103
141
  // Auto-detect business from .atris/business.json in current dir
104
142
  if (!slug || slug.startsWith('-')) {
105
143
  const bizFile = path.join(process.cwd(), '.atris', 'business.json');
@@ -113,7 +151,7 @@ async function pushAtris() {
113
151
  }
114
152
 
115
153
  if (!slug || slug === '--help') {
116
- console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete]');
154
+ console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete] [--delete-all]');
117
155
  console.log('');
118
156
  console.log(' Push requires a fresh pull. If cloud has changed since your last pull,');
119
157
  console.log(' the push will be blocked until you run `atris pull`. Use --force to override.');
@@ -122,14 +160,17 @@ async function pushAtris() {
122
160
  console.log(' atris push pallet Push pallet/ or atris/pallet/');
123
161
  console.log(' atris push pallet --only team/nate Only push files in team/nate/');
124
162
  console.log(' atris push --force Bypass freshness check (force-push, may overwrite cloud changes)');
125
- console.log(' atris push --delete Allow cloud deletes shown by --dry-run');
163
+ console.log(' atris push --delete Allow small cloud deletes shown by --dry-run');
164
+ console.log(' atris push --delete-all Extra confirmation for mass-delete recovery');
126
165
  process.exit(0);
127
166
  }
128
167
 
129
168
  const force = process.argv.includes('--force');
130
169
  const dryRun = process.argv.includes('--dry-run');
131
170
  const allowDelete = process.argv.includes('--delete');
171
+ const allowMassDelete = process.argv.includes('--delete-all');
132
172
  const allowCrossRootManifest = process.argv.includes('--allow-cross-root-manifest');
173
+ const timeoutSec = parsePushTimeoutSec(process.argv);
133
174
 
134
175
  // Parse --only
135
176
  let onlyRaw = null;
@@ -390,6 +431,24 @@ async function pushAtris() {
390
431
  process.exit(1);
391
432
  }
392
433
 
434
+ if (deletedPaths.length > 0 && allowDelete && !allowMassDelete && isMassDeletePlan({ deletedPaths, filesToPush, unchangedCount })) {
435
+ console.log('');
436
+ console.log(` ✗ Refusing mass delete of ${deletedPaths.length} cloud files with --delete alone.`);
437
+ console.log('');
438
+ console.log(' This looks like a missing local folder or wrong source root.');
439
+ console.log(' No cloud files were deleted.');
440
+ console.log('');
441
+ console.log(' First inspect the plan:');
442
+ console.log(` atris push ${resolvedSlug || slug} --dry-run`);
443
+ console.log('');
444
+ console.log(' If this is truly an intentional full cleanup, rerun with both flags:');
445
+ console.log(` atris push ${resolvedSlug || slug} --delete --delete-all`);
446
+ console.log('');
447
+ console.log(' If this is surprising, pull cloud truth first:');
448
+ console.log(` atris pull ${resolvedSlug || slug} --keep-local --timeout 120`);
449
+ process.exit(1);
450
+ }
451
+
393
452
  let pushed = 0;
394
453
  let deleted = 0;
395
454
  let skipped = [];
@@ -418,8 +477,14 @@ async function pushAtris() {
418
477
  };
419
478
  const wireFiles = (files) => files.map((f) => ({ path: toWirePath(f.path), content: f.content }));
420
479
  const syncFiles = (files) => apiRequestJson(
421
- `/business/${businessId}/workspaces/${workspaceId}/sync`,
422
- { method: 'POST', token: creds.token, body: { files: wireFiles(files) }, headers: { 'X-Atris-Actor-Source': 'cli' } }
480
+ `/business/${businessId}/workspaces/${workspaceId}/sync?timeout=${timeoutSec}`,
481
+ {
482
+ method: 'POST',
483
+ token: creds.token,
484
+ body: { files: wireFiles(files) },
485
+ headers: { 'X-Atris-Actor-Source': 'cli' },
486
+ timeoutMs: (timeoutSec + 15) * 1000,
487
+ }
423
488
  );
424
489
 
425
490
  // Inspect per-file results from a /sync response. Treat "written" and
@@ -682,4 +747,6 @@ module.exports = {
682
747
  canonicalWorkspaceRoot,
683
748
  basenameOfManifestPath,
684
749
  isBusinessWorkspaceRoot,
750
+ isMassDeletePlan,
751
+ parsePushTimeoutSec,
685
752
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atris",
3
- "version": "3.15.12",
3
+ "version": "3.15.14",
4
4
  "description": "Atris — an operating system for intelligence. Integrates with any agent.",
5
5
  "main": "bin/atris.js",
6
6
  "bin": {