@qodalis/cli-files 2.0.0-beta.9 → 2.0.0-rc.1

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.
@@ -8,7 +8,11 @@ var files = (() => {
8
8
  var __getProtoOf = Object.getPrototypeOf;
9
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
10
  var __commonJS = (cb, mod) => function __require() {
11
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ try {
12
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
+ } catch (e) {
14
+ throw mod = 0, e;
15
+ }
12
16
  };
13
17
  var __copyProps = (to, from, except, desc) => {
14
18
  if (from && typeof from === "object" || typeof from === "function") {
@@ -27,7 +31,7 @@ var files = (() => {
27
31
  mod
28
32
  ));
29
33
  var require_constants = __commonJS({
30
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/internal/constants.js"(exports$1, module2) {
34
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/constants.js"(exports, module2) {
31
35
  var SEMVER_SPEC_VERSION = "2.0.0";
32
36
  var MAX_LENGTH = 256;
33
37
  var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || /* istanbul ignore next */
@@ -56,26 +60,26 @@ var files = (() => {
56
60
  }
57
61
  });
58
62
  var require_debug = __commonJS({
59
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/internal/debug.js"(exports$1, module2) {
63
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/debug.js"(exports, module2) {
60
64
  var debug = typeof process === "object" && process.env && process.env.NODE_DEBUG && /\bsemver\b/i.test(process.env.NODE_DEBUG) ? (...args) => console.error("SEMVER", ...args) : () => {
61
65
  };
62
66
  module2.exports = debug;
63
67
  }
64
68
  });
65
69
  var require_re = __commonJS({
66
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/internal/re.js"(exports$1, module2) {
70
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/re.js"(exports, module2) {
67
71
  var {
68
72
  MAX_SAFE_COMPONENT_LENGTH,
69
73
  MAX_SAFE_BUILD_LENGTH,
70
74
  MAX_LENGTH
71
75
  } = require_constants();
72
76
  var debug = require_debug();
73
- exports$1 = module2.exports = {};
74
- var re = exports$1.re = [];
75
- var safeRe = exports$1.safeRe = [];
76
- var src = exports$1.src = [];
77
- var safeSrc = exports$1.safeSrc = [];
78
- var t = exports$1.t = {};
77
+ exports = module2.exports = {};
78
+ var re = exports.re = [];
79
+ var safeRe = exports.safeRe = [];
80
+ var src = exports.src = [];
81
+ var safeSrc = exports.safeSrc = [];
82
+ var t = exports.t = {};
79
83
  var R = 0;
80
84
  var LETTERDASHNUMBER = "[a-zA-Z0-9-]";
81
85
  var safeRegexReplacements = [
@@ -128,18 +132,18 @@ var files = (() => {
128
132
  createToken("COERCERTLFULL", src[t.COERCEFULL], true);
129
133
  createToken("LONETILDE", "(?:~>?)");
130
134
  createToken("TILDETRIM", `(\\s*)${src[t.LONETILDE]}\\s+`, true);
131
- exports$1.tildeTrimReplace = "$1~";
135
+ exports.tildeTrimReplace = "$1~";
132
136
  createToken("TILDE", `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`);
133
137
  createToken("TILDELOOSE", `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`);
134
138
  createToken("LONECARET", "(?:\\^)");
135
139
  createToken("CARETTRIM", `(\\s*)${src[t.LONECARET]}\\s+`, true);
136
- exports$1.caretTrimReplace = "$1^";
140
+ exports.caretTrimReplace = "$1^";
137
141
  createToken("CARET", `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`);
138
142
  createToken("CARETLOOSE", `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`);
139
143
  createToken("COMPARATORLOOSE", `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`);
140
144
  createToken("COMPARATOR", `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`);
141
145
  createToken("COMPARATORTRIM", `(\\s*)${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true);
142
- exports$1.comparatorTrimReplace = "$1$2$3";
146
+ exports.comparatorTrimReplace = "$1$2$3";
143
147
  createToken("HYPHENRANGE", `^\\s*(${src[t.XRANGEPLAIN]})\\s+-\\s+(${src[t.XRANGEPLAIN]})\\s*$`);
144
148
  createToken("HYPHENRANGELOOSE", `^\\s*(${src[t.XRANGEPLAINLOOSE]})\\s+-\\s+(${src[t.XRANGEPLAINLOOSE]})\\s*$`);
145
149
  createToken("STAR", "(<|>)?=?\\s*\\*");
@@ -148,7 +152,7 @@ var files = (() => {
148
152
  }
149
153
  });
150
154
  var require_parse_options = __commonJS({
151
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/internal/parse-options.js"(exports$1, module2) {
155
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/parse-options.js"(exports, module2) {
152
156
  var looseOption = Object.freeze({ loose: true });
153
157
  var emptyOpts = Object.freeze({});
154
158
  var parseOptions = (options) => {
@@ -164,7 +168,7 @@ var files = (() => {
164
168
  }
165
169
  });
166
170
  var require_identifiers = __commonJS({
167
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/internal/identifiers.js"(exports$1, module2) {
171
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/identifiers.js"(exports, module2) {
168
172
  var numeric = /^[0-9]+$/;
169
173
  var compareIdentifiers = (a, b) => {
170
174
  if (typeof a === "number" && typeof b === "number") {
@@ -186,12 +190,24 @@ var files = (() => {
186
190
  }
187
191
  });
188
192
  var require_semver = __commonJS({
189
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/classes/semver.js"(exports$1, module2) {
193
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/classes/semver.js"(exports, module2) {
190
194
  var debug = require_debug();
191
195
  var { MAX_LENGTH, MAX_SAFE_INTEGER } = require_constants();
192
196
  var { safeRe: re, t } = require_re();
193
197
  var parseOptions = require_parse_options();
194
198
  var { compareIdentifiers } = require_identifiers();
199
+ var isPrereleaseIdentifier = (prerelease, identifier) => {
200
+ const identifiers = identifier.split(".");
201
+ if (identifiers.length > prerelease.length) {
202
+ return false;
203
+ }
204
+ for (let i = 0; i < identifiers.length; i++) {
205
+ if (compareIdentifiers(prerelease[i], identifiers[i]) !== 0) {
206
+ return false;
207
+ }
208
+ }
209
+ return true;
210
+ };
195
211
  var SemVer = class _SemVer {
196
212
  constructor(version, options) {
197
213
  options = parseOptions(options);
@@ -438,8 +454,9 @@ var files = (() => {
438
454
  if (identifierBase === false) {
439
455
  prerelease = [identifier];
440
456
  }
441
- if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
442
- if (isNaN(this.prerelease[1])) {
457
+ if (isPrereleaseIdentifier(this.prerelease, identifier)) {
458
+ const prereleaseBase = this.prerelease[identifier.split(".").length];
459
+ if (isNaN(prereleaseBase)) {
443
460
  this.prerelease = prerelease;
444
461
  }
445
462
  } else {
@@ -462,7 +479,7 @@ var files = (() => {
462
479
  }
463
480
  });
464
481
  var require_parse = __commonJS({
465
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/parse.js"(exports$1, module2) {
482
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/parse.js"(exports, module2) {
466
483
  var SemVer = require_semver();
467
484
  var parse = (version, options, throwErrors = false) => {
468
485
  if (version instanceof SemVer) {
@@ -481,7 +498,7 @@ var files = (() => {
481
498
  }
482
499
  });
483
500
  var require_valid = __commonJS({
484
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/valid.js"(exports$1, module2) {
501
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/valid.js"(exports, module2) {
485
502
  var parse = require_parse();
486
503
  var valid2 = (version, options) => {
487
504
  const v = parse(version, options);
@@ -491,7 +508,7 @@ var files = (() => {
491
508
  }
492
509
  });
493
510
  var require_clean = __commonJS({
494
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/clean.js"(exports$1, module2) {
511
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/clean.js"(exports, module2) {
495
512
  var parse = require_parse();
496
513
  var clean = (version, options) => {
497
514
  const s = parse(version.trim().replace(/^[=v]+/, ""), options);
@@ -501,7 +518,7 @@ var files = (() => {
501
518
  }
502
519
  });
503
520
  var require_inc = __commonJS({
504
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/inc.js"(exports$1, module2) {
521
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/inc.js"(exports, module2) {
505
522
  var SemVer = require_semver();
506
523
  var inc = (version, release, options, identifier, identifierBase) => {
507
524
  if (typeof options === "string") {
@@ -522,7 +539,7 @@ var files = (() => {
522
539
  }
523
540
  });
524
541
  var require_diff = __commonJS({
525
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/diff.js"(exports$1, module2) {
542
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/diff.js"(exports, module2) {
526
543
  var parse = require_parse();
527
544
  var diff = (version1, version2) => {
528
545
  const v1 = parse(version1, null, true);
@@ -563,28 +580,28 @@ var files = (() => {
563
580
  }
564
581
  });
565
582
  var require_major = __commonJS({
566
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/major.js"(exports$1, module2) {
583
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/major.js"(exports, module2) {
567
584
  var SemVer = require_semver();
568
585
  var major = (a, loose) => new SemVer(a, loose).major;
569
586
  module2.exports = major;
570
587
  }
571
588
  });
572
589
  var require_minor = __commonJS({
573
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/minor.js"(exports$1, module2) {
590
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/minor.js"(exports, module2) {
574
591
  var SemVer = require_semver();
575
592
  var minor = (a, loose) => new SemVer(a, loose).minor;
576
593
  module2.exports = minor;
577
594
  }
578
595
  });
579
596
  var require_patch = __commonJS({
580
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/patch.js"(exports$1, module2) {
597
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/patch.js"(exports, module2) {
581
598
  var SemVer = require_semver();
582
599
  var patch = (a, loose) => new SemVer(a, loose).patch;
583
600
  module2.exports = patch;
584
601
  }
585
602
  });
586
603
  var require_prerelease = __commonJS({
587
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/prerelease.js"(exports$1, module2) {
604
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/prerelease.js"(exports, module2) {
588
605
  var parse = require_parse();
589
606
  var prerelease = (version, options) => {
590
607
  const parsed = parse(version, options);
@@ -594,28 +611,28 @@ var files = (() => {
594
611
  }
595
612
  });
596
613
  var require_compare = __commonJS({
597
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/compare.js"(exports$1, module2) {
614
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/compare.js"(exports, module2) {
598
615
  var SemVer = require_semver();
599
616
  var compare2 = (a, b, loose) => new SemVer(a, loose).compare(new SemVer(b, loose));
600
617
  module2.exports = compare2;
601
618
  }
602
619
  });
603
620
  var require_rcompare = __commonJS({
604
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/rcompare.js"(exports$1, module2) {
621
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/rcompare.js"(exports, module2) {
605
622
  var compare2 = require_compare();
606
623
  var rcompare = (a, b, loose) => compare2(b, a, loose);
607
624
  module2.exports = rcompare;
608
625
  }
609
626
  });
610
627
  var require_compare_loose = __commonJS({
611
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/compare-loose.js"(exports$1, module2) {
628
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/compare-loose.js"(exports, module2) {
612
629
  var compare2 = require_compare();
613
630
  var compareLoose = (a, b) => compare2(a, b, true);
614
631
  module2.exports = compareLoose;
615
632
  }
616
633
  });
617
634
  var require_compare_build = __commonJS({
618
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/compare-build.js"(exports$1, module2) {
635
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/compare-build.js"(exports, module2) {
619
636
  var SemVer = require_semver();
620
637
  var compareBuild = (a, b, loose) => {
621
638
  const versionA = new SemVer(a, loose);
@@ -626,63 +643,63 @@ var files = (() => {
626
643
  }
627
644
  });
628
645
  var require_sort = __commonJS({
629
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/sort.js"(exports$1, module2) {
646
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/sort.js"(exports, module2) {
630
647
  var compareBuild = require_compare_build();
631
- var sort = (list, loose) => list.sort((a, b) => compareBuild(a, b, loose));
648
+ var sort = (list2, loose) => list2.sort((a, b) => compareBuild(a, b, loose));
632
649
  module2.exports = sort;
633
650
  }
634
651
  });
635
652
  var require_rsort = __commonJS({
636
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/rsort.js"(exports$1, module2) {
653
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/rsort.js"(exports, module2) {
637
654
  var compareBuild = require_compare_build();
638
- var rsort = (list, loose) => list.sort((a, b) => compareBuild(b, a, loose));
655
+ var rsort = (list2, loose) => list2.sort((a, b) => compareBuild(b, a, loose));
639
656
  module2.exports = rsort;
640
657
  }
641
658
  });
642
659
  var require_gt = __commonJS({
643
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/gt.js"(exports$1, module2) {
660
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/gt.js"(exports, module2) {
644
661
  var compare2 = require_compare();
645
662
  var gt = (a, b, loose) => compare2(a, b, loose) > 0;
646
663
  module2.exports = gt;
647
664
  }
648
665
  });
649
666
  var require_lt = __commonJS({
650
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/lt.js"(exports$1, module2) {
667
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/lt.js"(exports, module2) {
651
668
  var compare2 = require_compare();
652
669
  var lt = (a, b, loose) => compare2(a, b, loose) < 0;
653
670
  module2.exports = lt;
654
671
  }
655
672
  });
656
673
  var require_eq = __commonJS({
657
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/eq.js"(exports$1, module2) {
674
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/eq.js"(exports, module2) {
658
675
  var compare2 = require_compare();
659
676
  var eq = (a, b, loose) => compare2(a, b, loose) === 0;
660
677
  module2.exports = eq;
661
678
  }
662
679
  });
663
680
  var require_neq = __commonJS({
664
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/neq.js"(exports$1, module2) {
681
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/neq.js"(exports, module2) {
665
682
  var compare2 = require_compare();
666
683
  var neq = (a, b, loose) => compare2(a, b, loose) !== 0;
667
684
  module2.exports = neq;
668
685
  }
669
686
  });
670
687
  var require_gte = __commonJS({
671
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/gte.js"(exports$1, module2) {
688
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/gte.js"(exports, module2) {
672
689
  var compare2 = require_compare();
673
690
  var gte2 = (a, b, loose) => compare2(a, b, loose) >= 0;
674
691
  module2.exports = gte2;
675
692
  }
676
693
  });
677
694
  var require_lte = __commonJS({
678
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/lte.js"(exports$1, module2) {
695
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/lte.js"(exports, module2) {
679
696
  var compare2 = require_compare();
680
697
  var lte = (a, b, loose) => compare2(a, b, loose) <= 0;
681
698
  module2.exports = lte;
682
699
  }
683
700
  });
684
701
  var require_cmp = __commonJS({
685
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/cmp.js"(exports$1, module2) {
702
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/cmp.js"(exports, module2) {
686
703
  var eq = require_eq();
687
704
  var neq = require_neq();
688
705
  var gt = require_gt();
@@ -729,7 +746,7 @@ var files = (() => {
729
746
  }
730
747
  });
731
748
  var require_coerce = __commonJS({
732
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/coerce.js"(exports$1, module2) {
749
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/coerce.js"(exports, module2) {
733
750
  var SemVer = require_semver();
734
751
  var parse = require_parse();
735
752
  var { safeRe: re, t } = require_re();
@@ -771,8 +788,46 @@ var files = (() => {
771
788
  module2.exports = coerce2;
772
789
  }
773
790
  });
791
+ var require_truncate = __commonJS({
792
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/truncate.js"(exports, module2) {
793
+ var parse = require_parse();
794
+ var constants2 = require_constants();
795
+ var SemVer = require_semver();
796
+ var truncate = (version, truncation, options) => {
797
+ if (!constants2.RELEASE_TYPES.includes(truncation)) {
798
+ return null;
799
+ }
800
+ const clonedVersion = cloneInputVersion(version, options);
801
+ return clonedVersion && doTruncation(clonedVersion, truncation);
802
+ };
803
+ var cloneInputVersion = (version, options) => {
804
+ const versionStringToParse = version instanceof SemVer ? version.version : version;
805
+ return parse(versionStringToParse, options);
806
+ };
807
+ var doTruncation = (version, truncation) => {
808
+ if (isPrerelease(truncation)) {
809
+ return version.version;
810
+ }
811
+ version.prerelease = [];
812
+ switch (truncation) {
813
+ case "major":
814
+ version.minor = 0;
815
+ version.patch = 0;
816
+ break;
817
+ case "minor":
818
+ version.patch = 0;
819
+ break;
820
+ }
821
+ return version.format();
822
+ };
823
+ var isPrerelease = (type) => {
824
+ return type.startsWith("pre");
825
+ };
826
+ module2.exports = truncate;
827
+ }
828
+ });
774
829
  var require_lrucache = __commonJS({
775
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/internal/lrucache.js"(exports$1, module2) {
830
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/lrucache.js"(exports, module2) {
776
831
  var LRUCache = class {
777
832
  constructor() {
778
833
  this.max = 1e3;
@@ -807,7 +862,7 @@ var files = (() => {
807
862
  }
808
863
  });
809
864
  var require_range = __commonJS({
810
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/classes/range.js"(exports$1, module2) {
865
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/classes/range.js"(exports, module2) {
811
866
  var SPACE_CHARACTERS = /\s+/g;
812
867
  var Range = class _Range {
813
868
  constructor(range, options) {
@@ -874,6 +929,7 @@ var files = (() => {
874
929
  return this.range;
875
930
  }
876
931
  parseRange(range) {
932
+ range = range.replace(BUILDSTRIPRE, "");
877
933
  const memoOpts = (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | (this.options.loose && FLAG_LOOSE);
878
934
  const memoKey = memoOpts + ":" + range;
879
935
  const cached = cache.get(memoKey);
@@ -956,12 +1012,14 @@ var files = (() => {
956
1012
  var SemVer = require_semver();
957
1013
  var {
958
1014
  safeRe: re,
1015
+ src,
959
1016
  t,
960
1017
  comparatorTrimReplace,
961
1018
  tildeTrimReplace,
962
1019
  caretTrimReplace
963
1020
  } = require_re();
964
1021
  var { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require_constants();
1022
+ var BUILDSTRIPRE = new RegExp(src[t.BUILD], "g");
965
1023
  var isNullSet = (c) => c.value === "<0.0.0-0";
966
1024
  var isAny = (c) => c.value === "";
967
1025
  var isSatisfiable = (comparators, options) => {
@@ -990,20 +1048,22 @@ var files = (() => {
990
1048
  return comp;
991
1049
  };
992
1050
  var isX = (id) => !id || id.toLowerCase() === "x" || id === "*";
1051
+ var invalidXRangeOrder = (M, m, p) => isX(M) && !isX(m) || isX(m) && p && !isX(p);
993
1052
  var replaceTildes = (comp, options) => {
994
1053
  return comp.trim().split(/\s+/).map((c) => replaceTilde(c, options)).join(" ");
995
1054
  };
996
1055
  var replaceTilde = (comp, options) => {
997
1056
  const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE];
1057
+ const z = options.includePrerelease ? "-0" : "";
998
1058
  return comp.replace(r, (_, M, m, p, pr) => {
999
1059
  debug("tilde", comp, _, M, m, p, pr);
1000
1060
  let ret;
1001
1061
  if (isX(M)) {
1002
1062
  ret = "";
1003
1063
  } else if (isX(m)) {
1004
- ret = `>=${M}.0.0 <${+M + 1}.0.0-0`;
1064
+ ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`;
1005
1065
  } else if (isX(p)) {
1006
- ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0`;
1066
+ ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`;
1007
1067
  } else if (pr) {
1008
1068
  debug("replaceTilde pr", pr);
1009
1069
  ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`;
@@ -1049,9 +1109,9 @@ var files = (() => {
1049
1109
  debug("no pr");
1050
1110
  if (M === "0") {
1051
1111
  if (m === "0") {
1052
- ret = `>=${M}.${m}.${p}${z} <${M}.${m}.${+p + 1}-0`;
1112
+ ret = `>=${M}.${m}.${p} <${M}.${m}.${+p + 1}-0`;
1053
1113
  } else {
1054
- ret = `>=${M}.${m}.${p}${z} <${M}.${+m + 1}.0-0`;
1114
+ ret = `>=${M}.${m}.${p} <${M}.${+m + 1}.0-0`;
1055
1115
  }
1056
1116
  } else {
1057
1117
  ret = `>=${M}.${m}.${p} <${+M + 1}.0.0-0`;
@@ -1070,6 +1130,9 @@ var files = (() => {
1070
1130
  const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE];
1071
1131
  return comp.replace(r, (ret, gtlt, M, m, p, pr) => {
1072
1132
  debug("xRange", comp, ret, gtlt, M, m, p, pr);
1133
+ if (invalidXRangeOrder(M, m, p)) {
1134
+ return comp;
1135
+ }
1073
1136
  const xM = isX(M);
1074
1137
  const xm = xM || isX(m);
1075
1138
  const xp = xm || isX(p);
@@ -1181,7 +1244,7 @@ var files = (() => {
1181
1244
  }
1182
1245
  });
1183
1246
  var require_comparator = __commonJS({
1184
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/classes/comparator.js"(exports$1, module2) {
1247
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/classes/comparator.js"(exports, module2) {
1185
1248
  var ANY = /* @__PURE__ */ Symbol("SemVer ANY");
1186
1249
  var Comparator = class _Comparator {
1187
1250
  static get ANY() {
@@ -1291,7 +1354,7 @@ var files = (() => {
1291
1354
  }
1292
1355
  });
1293
1356
  var require_satisfies = __commonJS({
1294
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/functions/satisfies.js"(exports$1, module2) {
1357
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/satisfies.js"(exports, module2) {
1295
1358
  var Range = require_range();
1296
1359
  var satisfies2 = (version, range, options) => {
1297
1360
  try {
@@ -1305,14 +1368,14 @@ var files = (() => {
1305
1368
  }
1306
1369
  });
1307
1370
  var require_to_comparators = __commonJS({
1308
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/to-comparators.js"(exports$1, module2) {
1371
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/to-comparators.js"(exports, module2) {
1309
1372
  var Range = require_range();
1310
1373
  var toComparators = (range, options) => new Range(range, options).set.map((comp) => comp.map((c) => c.value).join(" ").trim().split(" "));
1311
1374
  module2.exports = toComparators;
1312
1375
  }
1313
1376
  });
1314
1377
  var require_max_satisfying = __commonJS({
1315
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/max-satisfying.js"(exports$1, module2) {
1378
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/max-satisfying.js"(exports, module2) {
1316
1379
  var SemVer = require_semver();
1317
1380
  var Range = require_range();
1318
1381
  var maxSatisfying = (versions, range, options) => {
@@ -1338,7 +1401,7 @@ var files = (() => {
1338
1401
  }
1339
1402
  });
1340
1403
  var require_min_satisfying = __commonJS({
1341
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/min-satisfying.js"(exports$1, module2) {
1404
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/min-satisfying.js"(exports, module2) {
1342
1405
  var SemVer = require_semver();
1343
1406
  var Range = require_range();
1344
1407
  var minSatisfying = (versions, range, options) => {
@@ -1364,7 +1427,7 @@ var files = (() => {
1364
1427
  }
1365
1428
  });
1366
1429
  var require_min_version = __commonJS({
1367
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/min-version.js"(exports$1, module2) {
1430
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/min-version.js"(exports, module2) {
1368
1431
  var SemVer = require_semver();
1369
1432
  var Range = require_range();
1370
1433
  var gt = require_gt();
@@ -1420,7 +1483,7 @@ var files = (() => {
1420
1483
  }
1421
1484
  });
1422
1485
  var require_valid2 = __commonJS({
1423
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/valid.js"(exports$1, module2) {
1486
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/valid.js"(exports, module2) {
1424
1487
  var Range = require_range();
1425
1488
  var validRange = (range, options) => {
1426
1489
  try {
@@ -1433,7 +1496,7 @@ var files = (() => {
1433
1496
  }
1434
1497
  });
1435
1498
  var require_outside = __commonJS({
1436
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/outside.js"(exports$1, module2) {
1499
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/outside.js"(exports, module2) {
1437
1500
  var SemVer = require_semver();
1438
1501
  var Comparator = require_comparator();
1439
1502
  var { ANY } = Comparator;
@@ -1499,21 +1562,21 @@ var files = (() => {
1499
1562
  }
1500
1563
  });
1501
1564
  var require_gtr = __commonJS({
1502
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/gtr.js"(exports$1, module2) {
1565
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/gtr.js"(exports, module2) {
1503
1566
  var outside = require_outside();
1504
1567
  var gtr = (version, range, options) => outside(version, range, ">", options);
1505
1568
  module2.exports = gtr;
1506
1569
  }
1507
1570
  });
1508
1571
  var require_ltr = __commonJS({
1509
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/ltr.js"(exports$1, module2) {
1572
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/ltr.js"(exports, module2) {
1510
1573
  var outside = require_outside();
1511
1574
  var ltr = (version, range, options) => outside(version, range, "<", options);
1512
1575
  module2.exports = ltr;
1513
1576
  }
1514
1577
  });
1515
1578
  var require_intersects = __commonJS({
1516
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/intersects.js"(exports$1, module2) {
1579
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/intersects.js"(exports, module2) {
1517
1580
  var Range = require_range();
1518
1581
  var intersects = (r1, r2, options) => {
1519
1582
  r1 = new Range(r1, options);
@@ -1524,7 +1587,7 @@ var files = (() => {
1524
1587
  }
1525
1588
  });
1526
1589
  var require_simplify = __commonJS({
1527
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/simplify.js"(exports$1, module2) {
1590
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/simplify.js"(exports, module2) {
1528
1591
  var satisfies2 = require_satisfies();
1529
1592
  var compare2 = require_compare();
1530
1593
  module2.exports = (versions, range, options) => {
@@ -1571,7 +1634,7 @@ var files = (() => {
1571
1634
  }
1572
1635
  });
1573
1636
  var require_subset = __commonJS({
1574
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/ranges/subset.js"(exports$1, module2) {
1637
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/subset.js"(exports, module2) {
1575
1638
  var Range = require_range();
1576
1639
  var Comparator = require_comparator();
1577
1640
  var { ANY } = Comparator;
@@ -1678,7 +1741,7 @@ var files = (() => {
1678
1741
  if (higher === c && higher !== gt) {
1679
1742
  return false;
1680
1743
  }
1681
- } else if (gt.operator === ">=" && !satisfies2(gt.semver, String(c), options)) {
1744
+ } else if (gt.operator === ">=" && !c.test(gt.semver)) {
1682
1745
  return false;
1683
1746
  }
1684
1747
  }
@@ -1693,7 +1756,7 @@ var files = (() => {
1693
1756
  if (lower === c && lower !== lt) {
1694
1757
  return false;
1695
1758
  }
1696
- } else if (lt.operator === "<=" && !satisfies2(lt.semver, String(c), options)) {
1759
+ } else if (lt.operator === "<=" && !c.test(lt.semver)) {
1697
1760
  return false;
1698
1761
  }
1699
1762
  }
@@ -1730,7 +1793,7 @@ var files = (() => {
1730
1793
  }
1731
1794
  });
1732
1795
  var require_semver2 = __commonJS({
1733
- "../../node_modules/.pnpm/semver@7.7.4/node_modules/semver/index.js"(exports$1, module2) {
1796
+ "../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/index.js"(exports, module2) {
1734
1797
  var internalRe = require_re();
1735
1798
  var constants2 = require_constants();
1736
1799
  var SemVer = require_semver();
@@ -1758,6 +1821,7 @@ var files = (() => {
1758
1821
  var lte = require_lte();
1759
1822
  var cmp = require_cmp();
1760
1823
  var coerce2 = require_coerce();
1824
+ var truncate = require_truncate();
1761
1825
  var Comparator = require_comparator();
1762
1826
  var Range = require_range();
1763
1827
  var satisfies2 = require_satisfies();
@@ -1796,6 +1860,7 @@ var files = (() => {
1796
1860
  lte,
1797
1861
  cmp,
1798
1862
  coerce: coerce2,
1863
+ truncate,
1799
1864
  Comparator,
1800
1865
  Range,
1801
1866
  satisfies: satisfies2,
@@ -1833,11 +1898,94 @@ var files = (() => {
1833
1898
  CliForegroundColor2["Reset"] = "\x1B[0m";
1834
1899
  return CliForegroundColor2;
1835
1900
  })(CliForegroundColor || {});
1836
- var ICliCompletionProvider_TOKEN = "cli-completion-provider";
1901
+ function createCliToken(name) {
1902
+ return name;
1903
+ }
1904
+ var ICliUserSessionService_TOKEN = createCliToken("cli-user-session-service");
1905
+ var ICliUsersStoreService_TOKEN = createCliToken("cli-users-store-service");
1906
+ var ICliGroupsStoreService_TOKEN = createCliToken("cli-groups-store-service");
1907
+ var ICliAuthService_TOKEN = createCliToken("cli-auth-service");
1908
+ var ICliCompletionProvider_TOKEN = createCliToken("cli-completion-provider");
1909
+ var ICliDragDropService_TOKEN = createCliToken("cli-drag-drop-service");
1910
+ var ICliFileTransferService_TOKEN = createCliToken("cli-file-transfer-service");
1911
+ var ICliTranslationService_TOKEN = createCliToken("cli-translation-service");
1912
+ var ICliPermissionService_TOKEN = createCliToken("cli-permission-service");
1913
+ var ICliFilePickerProvider_TOKEN = createCliToken("cli-file-picker-provider");
1914
+ var ICliHttpClient_TOKEN = createCliToken("cli-http-client");
1915
+ var ICliUserEventService_TOKEN = createCliToken("cli-user-event-service");
1916
+ var ICliAuditService_TOKEN = createCliToken("cli-audit-service");
1917
+ var ICliAuthorizationService_TOKEN = createCliToken("cli-authorization-service");
1918
+ var ICliPermissionRegistry_TOKEN = createCliToken("cli-permission-registry");
1919
+ var ICliSessionsService_TOKEN = createCliToken("cli-sessions-service");
1920
+ var ICliKeyValueStore_TOKEN = createCliToken("cli-key-value-store");
1921
+ var ICliServerAuthTokenProvider_TOKEN = createCliToken("cli-server-auth-token-provider");
1922
+ var ICliServerAuthService_TOKEN = createCliToken("cli-server-auth-service");
1923
+ var ICliServerManager_TOKEN = createCliToken("cli-server-manager");
1924
+ function resolveHeaders(provider) {
1925
+ if (!provider) return {};
1926
+ return typeof provider === "function" ? provider() : provider;
1927
+ }
1928
+ var ICliCommandGuard_TOKEN = createCliToken("cli-command-guard");
1929
+ var CliAccessDeniedError = class _CliAccessDeniedError extends Error {
1930
+ constructor(message = "permission denied") {
1931
+ super(message);
1932
+ this.code = "EACCES";
1933
+ this.name = "CliAccessDeniedError";
1934
+ Object.setPrototypeOf(this, _CliAccessDeniedError.prototype);
1935
+ }
1936
+ };
1937
+ function isCliAccessDeniedError(error) {
1938
+ return error instanceof CliAccessDeniedError || !!error && typeof error === "object" && error.name === "CliAccessDeniedError";
1939
+ }
1940
+ var ICliEventBus_TOKEN = createCliToken("cli-event-bus");
1837
1941
  var DefaultLibraryAuthor = {
1838
1942
  name: "Nicolae Lupei",
1839
1943
  email: "nicolae.lupei@qodalis.com"
1840
1944
  };
1945
+ function splitShellWords(text) {
1946
+ const words = [];
1947
+ let current = "";
1948
+ let inWord = false;
1949
+ for (let i = 0; i < text.length; i++) {
1950
+ const ch = text[i];
1951
+ if (ch === "\\" && i + 1 < text.length) {
1952
+ current += text[++i];
1953
+ inWord = true;
1954
+ } else if (ch === "'" && text.indexOf("'", i + 1) !== -1) {
1955
+ const end = text.indexOf("'", i + 1);
1956
+ current += text.slice(i + 1, end);
1957
+ inWord = true;
1958
+ i = end;
1959
+ } else if (ch === '"' && closingDoubleQuote(text, i) !== -1) {
1960
+ const end = closingDoubleQuote(text, i);
1961
+ current += unescapeDoubleQuoted(text.slice(i + 1, end));
1962
+ inWord = true;
1963
+ i = end;
1964
+ } else if (/\s/.test(ch)) {
1965
+ if (inWord) words.push(current);
1966
+ current = "";
1967
+ inWord = false;
1968
+ } else {
1969
+ current += ch;
1970
+ inWord = true;
1971
+ }
1972
+ }
1973
+ if (inWord) words.push(current);
1974
+ return words;
1975
+ }
1976
+ function joinShellWords(text) {
1977
+ return splitShellWords(text).join(" ");
1978
+ }
1979
+ function closingDoubleQuote(text, start) {
1980
+ for (let i = start + 1; i < text.length; i++) {
1981
+ if (text[i] === "\\") i++;
1982
+ else if (text[i] === '"') return i;
1983
+ }
1984
+ return -1;
1985
+ }
1986
+ function unescapeDoubleQuoted(text) {
1987
+ return text.replace(/\\(["\\$`])/g, "$1");
1988
+ }
1841
1989
  var semver = __toESM(require_semver2());
1842
1990
  var API_VERSION = 2;
1843
1991
  var VERSIONS_ENDPOINT = "/api/qcli/versions";
@@ -1860,9 +2008,18 @@ var files = (() => {
1860
2008
  * Returns the negotiated API version and the base path for all subsequent calls,
1861
2009
  * or null if the server is unreachable or incompatible.
1862
2010
  */
1863
- static async discover(baseUrl) {
2011
+ static async discover(baseUrl, headers, options) {
2012
+ let response;
2013
+ try {
2014
+ const resolved = resolveHeaders(headers);
2015
+ response = await fetch(`${baseUrl}${VERSIONS_ENDPOINT}`, {
2016
+ headers: Object.keys(resolved).length > 0 ? resolved : void 0
2017
+ });
2018
+ } catch (e) {
2019
+ if (options?.rethrowNetworkErrors) throw e;
2020
+ return null;
2021
+ }
1864
2022
  try {
1865
- const response = await fetch(`${baseUrl}${VERSIONS_ENDPOINT}`);
1866
2023
  if (!response.ok) return null;
1867
2024
  const info = await response.json();
1868
2025
  const version = this.negotiate(info);
@@ -1876,6 +2033,60 @@ var files = (() => {
1876
2033
  }
1877
2034
  }
1878
2035
  };
2036
+ function writeColumns(context, rows, formatLeft, formatRight) {
2037
+ const width = Math.max(...rows.map(([left]) => left.length)) + 2;
2038
+ for (const [left, right] of rows) {
2039
+ context.writer.writeln(
2040
+ ` ${formatLeft(left.padEnd(width))}${formatRight(right)}`
2041
+ );
2042
+ }
2043
+ }
2044
+ function writeUsage(context, rows) {
2045
+ const { writer, translator } = context;
2046
+ writer.writeln(`\u{1F4CB} ${translator.t("cli.common.usage", "Usage:")}`);
2047
+ writeColumns(
2048
+ context,
2049
+ rows,
2050
+ (cmd) => writer.wrapInColor(
2051
+ cmd,
2052
+ "\x1B[36m"
2053
+ /* Cyan */
2054
+ ),
2055
+ (text) => text
2056
+ );
2057
+ }
2058
+ function writeExamples(context, rows) {
2059
+ const { writer, translator } = context;
2060
+ writer.writeln(`\u{1F4DD} ${translator.t("cli.common.examples", "Examples:")}`);
2061
+ writeColumns(
2062
+ context,
2063
+ rows,
2064
+ (cmd) => cmd,
2065
+ (comment) => writer.wrapInColor(
2066
+ `# ${comment}`,
2067
+ "\x1B[32m"
2068
+ /* Green */
2069
+ )
2070
+ );
2071
+ }
2072
+ function writeWrapped(context, text, indent = 0) {
2073
+ const cols = context.terminal?.cols ?? 80;
2074
+ const width = Math.max(20, Math.min(cols - 1, 100) - indent);
2075
+ const pad = " ".repeat(indent);
2076
+ let line = "";
2077
+ for (const word of text.split(/\s+/).filter(Boolean)) {
2078
+ if (line && visibleLength2(line) + 1 + visibleLength2(word) > width) {
2079
+ context.writer.writeln(pad + line);
2080
+ line = word;
2081
+ } else {
2082
+ line = line ? `${line} ${word}` : word;
2083
+ }
2084
+ }
2085
+ if (line) context.writer.writeln(pad + line);
2086
+ }
2087
+ function visibleLength2(text) {
2088
+ return text.replace(/\x1b\[[0-9;]*m/g, "").length;
2089
+ }
1879
2090
  var bootCliModule = async (module2) => {
1880
2091
  if (typeof window !== "undefined" && window.__cliModuleRegistry) {
1881
2092
  await window.__cliModuleRegistry.register(module2);
@@ -2694,11 +2905,11 @@ var files = (() => {
2694
2905
  };
2695
2906
 
2696
2907
  // src/lib/version.ts
2697
- var LIBRARY_VERSION = "2.0.0-beta.9";
2908
+ var LIBRARY_VERSION = "2.0.0-rc.1";
2698
2909
  var API_VERSION2 = 2;
2699
2910
 
2700
2911
  // src/lib/interfaces/i-file-system-service.ts
2701
- var IFileSystemService_TOKEN = "cli-file-system-service";
2912
+ var IFileSystemService_TOKEN = createCliToken("cli-file-system-service");
2702
2913
 
2703
2914
  // src/lib/services/indexed-db-file-system.service.ts
2704
2915
  var DB_NAME = "qodalis-cli-filesystem";
@@ -2707,6 +2918,7 @@ var files = (() => {
2707
2918
  var CWD_KEY = "cwd";
2708
2919
  var HOME_KEY = "home";
2709
2920
  var DEFAULT_HOME = "/";
2921
+ var ANONYMOUS_OWNERSHIP = { uid: "nobody", gid: "nogroup" };
2710
2922
  function createSeedFileSystem() {
2711
2923
  const now = Date.now();
2712
2924
  return {
@@ -2754,6 +2966,88 @@ var files = (() => {
2754
2966
  this.homePath = DEFAULT_HOME;
2755
2967
  this.currentUid = null;
2756
2968
  this.currentGroups = [];
2969
+ this.systemDepth = 0;
2970
+ }
2971
+ // --- Access control ---
2972
+ setAccessPolicy(policy) {
2973
+ this.accessPolicy = policy;
2974
+ }
2975
+ runAsSystem(fn) {
2976
+ this.systemDepth++;
2977
+ try {
2978
+ return fn();
2979
+ } finally {
2980
+ this.systemDepth--;
2981
+ }
2982
+ }
2983
+ reassignOwner(fromUid, toUid) {
2984
+ const from = fromUid.toLowerCase();
2985
+ if (!from || !toUid) return 0;
2986
+ let changed = 0;
2987
+ const visit = (node) => {
2988
+ if (node.ownership && node.ownership.uid.toLowerCase() === from) {
2989
+ node.ownership = { ...node.ownership, uid: toUid };
2990
+ changed++;
2991
+ }
2992
+ node.children?.forEach(visit);
2993
+ };
2994
+ visit(this.root);
2995
+ return changed;
2996
+ }
2997
+ canTraverse(path) {
2998
+ try {
2999
+ this.assertTraverse(this.resolvePath(path), "stat", path);
3000
+ return true;
3001
+ } catch {
3002
+ return false;
3003
+ }
3004
+ }
3005
+ get enforcing() {
3006
+ return !!this.accessPolicy && this.systemDepth === 0;
3007
+ }
3008
+ deny(op, path, reason = "Permission denied") {
3009
+ throw new CliAccessDeniedError(`${op}: ${path}: ${reason}`);
3010
+ }
3011
+ /** `x` on every existing directory on the way to `resolved` (excluding the target). */
3012
+ assertTraverse(resolved, op, display) {
3013
+ if (!this.enforcing) return;
3014
+ const parts = resolved.split("/").filter(Boolean);
3015
+ let current = this.root;
3016
+ for (let i = 0; i < parts.length; i++) {
3017
+ if (!current || current.type !== "directory") return;
3018
+ if (!this.accessPolicy.canAccess(current, "execute")) this.deny(op, display);
3019
+ current = current.children?.find((c) => c.name === parts[i]);
3020
+ }
3021
+ }
3022
+ assertAccess(node, action, op, display) {
3023
+ if (this.enforcing && !this.accessPolicy.canAccess(node, action)) this.deny(op, display);
3024
+ }
3025
+ /** Recursive delete: every directory inside must allow removing its entries (`w` + `x`, readable). */
3026
+ assertRemovableTree(dir, dirPath, display) {
3027
+ if (!this.enforcing) return;
3028
+ this.assertAccess(dir, "read", "rm", display);
3029
+ this.assertAccess(dir, "execute", "rm", display);
3030
+ if ((dir.children?.length ?? 0) > 0) this.assertAccess(dir, "write", "rm", display);
3031
+ for (const child of dir.children ?? []) {
3032
+ if (child.type === "directory") this.assertRemovableTree(child, `${dirPath}/${child.name}`, display);
3033
+ }
3034
+ }
3035
+ /** Copying reads everything it copies: `r` on every file, `r` + `x` on every directory. */
3036
+ assertReadableTree(node, display) {
3037
+ if (!this.enforcing) return;
3038
+ this.assertAccess(node, "read", "cp", display);
3039
+ if (node.type !== "directory") return;
3040
+ this.assertAccess(node, "execute", "cp", display);
3041
+ for (const child of node.children ?? []) {
3042
+ this.assertReadableTree(child, `${display.replace(/\/+$/, "")}/${child.name}`);
3043
+ }
3044
+ }
3045
+ /** Creating/removing entries needs `w` and `x` on the containing directory. */
3046
+ assertModifyDir(dir, dirPath, op, display) {
3047
+ if (!this.enforcing) return;
3048
+ this.assertTraverse(dirPath, op, display);
3049
+ this.assertAccess(dir, "execute", op, display);
3050
+ this.assertAccess(dir, "write", op, display);
2757
3051
  }
2758
3052
  // --- User context ---
2759
3053
  setCurrentUser(uid, groups) {
@@ -2761,7 +3055,9 @@ var files = (() => {
2761
3055
  this.currentGroups = groups;
2762
3056
  }
2763
3057
  getDefaultOwnership() {
2764
- if (!this.currentUid) return void 0;
3058
+ if (!this.currentUid) {
3059
+ return this.accessPolicy ? { ...ANONYMOUS_OWNERSHIP } : void 0;
3060
+ }
2765
3061
  return {
2766
3062
  uid: this.currentUid,
2767
3063
  gid: this.currentGroups[0] || "users"
@@ -2780,6 +3076,8 @@ var files = (() => {
2780
3076
  if (node.type !== "directory") {
2781
3077
  throw new Error(`cd: ${path}: Not a directory`);
2782
3078
  }
3079
+ this.assertTraverse(resolved, "cd", path);
3080
+ this.assertAccess(node, "execute", "cd", path);
2783
3081
  this.cwd = resolved;
2784
3082
  }
2785
3083
  getHomePath() {
@@ -2836,6 +3134,8 @@ var files = (() => {
2836
3134
  if (node.type !== "directory") {
2837
3135
  throw new Error(`ls: ${path}: Not a directory`);
2838
3136
  }
3137
+ this.assertTraverse(this.resolvePath(path), "ls", path);
3138
+ this.assertAccess(node, "read", "ls", path);
2839
3139
  return node.children || [];
2840
3140
  }
2841
3141
  readFile(path) {
@@ -2846,6 +3146,8 @@ var files = (() => {
2846
3146
  if (node.type === "directory") {
2847
3147
  throw new Error(`cat: ${path}: Is a directory`);
2848
3148
  }
3149
+ this.assertTraverse(this.resolvePath(path), "cat", path);
3150
+ this.assertAccess(node, "read", "cat", path);
2849
3151
  return node.content ?? "";
2850
3152
  }
2851
3153
  exists(path) {
@@ -2870,6 +3172,10 @@ var files = (() => {
2870
3172
  }
2871
3173
  let child = current.children.find((c) => c.name === part);
2872
3174
  if (!child) {
3175
+ if (this.enforcing) {
3176
+ this.assertAccess(current, "execute", "mkdir", path);
3177
+ this.assertAccess(current, "write", "mkdir", path);
3178
+ }
2873
3179
  const now = Date.now();
2874
3180
  child = {
2875
3181
  name: part,
@@ -2885,6 +3191,8 @@ var files = (() => {
2885
3191
  current.modifiedAt = now;
2886
3192
  } else if (child.type !== "directory") {
2887
3193
  throw new Error(`mkdir: ${part}: Not a directory`);
3194
+ } else {
3195
+ this.assertAccess(child, "execute", "mkdir", path);
2888
3196
  }
2889
3197
  current = child;
2890
3198
  }
@@ -2903,6 +3211,7 @@ var files = (() => {
2903
3211
  if (parent.children?.find((c) => c.name === dirName)) {
2904
3212
  throw new Error(`mkdir: ${path}: File exists`);
2905
3213
  }
3214
+ this.assertModifyDir(parent, parentPath, "mkdir", path);
2906
3215
  const now = Date.now();
2907
3216
  if (!parent.children) {
2908
3217
  parent.children = [];
@@ -2937,6 +3246,12 @@ var files = (() => {
2937
3246
  }
2938
3247
  const existing = parent.children?.find((c) => c.name === fileName);
2939
3248
  const now = Date.now();
3249
+ if (existing) {
3250
+ this.assertTraverse(resolved, "touch", path);
3251
+ this.assertAccess(existing, "write", "touch", path);
3252
+ } else {
3253
+ this.assertModifyDir(parent, parentPath, "touch", path);
3254
+ }
2940
3255
  if (existing) {
2941
3256
  existing.modifiedAt = now;
2942
3257
  } else {
@@ -2963,6 +3278,8 @@ var files = (() => {
2963
3278
  if (node.type === "directory") {
2964
3279
  throw new Error(`write: ${path}: Is a directory`);
2965
3280
  }
3281
+ this.assertTraverse(resolved, "write", path);
3282
+ this.assertAccess(node, "write", "write", path);
2966
3283
  const now = Date.now();
2967
3284
  node.content = append ? (node.content ?? "") + content : content;
2968
3285
  node.size = new Blob([node.content]).size;
@@ -2991,6 +3308,8 @@ var files = (() => {
2991
3308
  if (target.type === "directory" && !recursive) {
2992
3309
  throw new Error(`rm: ${path}: Is a directory`);
2993
3310
  }
3311
+ this.assertModifyDir(parent, parentPath, "rm", path);
3312
+ if (target.type === "directory") this.assertRemovableTree(target, resolved, path);
2994
3313
  parent.children.splice(index, 1);
2995
3314
  parent.modifiedAt = Date.now();
2996
3315
  }
@@ -3005,10 +3324,13 @@ var files = (() => {
3005
3324
  `cp: -r not specified; omitting directory '${src}'`
3006
3325
  );
3007
3326
  }
3327
+ this.assertTraverse(srcResolved, "cp", src);
3328
+ this.assertReadableTree(srcNode, src);
3008
3329
  const destResolved = this.resolvePath(dest);
3009
3330
  const destNode = this.getNode(destResolved);
3010
3331
  if (destNode && destNode.type === "directory") {
3011
- const clone = this.cloneNode(srcNode);
3332
+ this.assertModifyDir(destNode, destResolved, "cp", dest);
3333
+ const clone = this.ownedByCurrentUser(this.cloneNode(srcNode));
3012
3334
  if (!destNode.children) {
3013
3335
  destNode.children = [];
3014
3336
  }
@@ -3028,7 +3350,8 @@ var files = (() => {
3028
3350
  if (!parent || parent.type !== "directory") {
3029
3351
  throw new Error(`cp: ${parentPath}: No such file or directory`);
3030
3352
  }
3031
- const clone = this.cloneNode(srcNode);
3353
+ this.assertModifyDir(parent, parentPath, "cp", dest);
3354
+ const clone = this.ownedByCurrentUser(this.cloneNode(srcNode));
3032
3355
  clone.name = newName;
3033
3356
  if (!parent.children) {
3034
3357
  parent.children = [];
@@ -3054,16 +3377,23 @@ var files = (() => {
3054
3377
  }
3055
3378
  const destResolved = this.resolvePath(dest);
3056
3379
  const destNode = this.getNode(destResolved);
3380
+ if (srcNode.type === "directory" && (destResolved === srcResolved || destResolved.startsWith(srcResolved + "/"))) {
3381
+ throw new Error(
3382
+ `mv: cannot move '${src}' to a subdirectory of itself, '${dest}'`
3383
+ );
3384
+ }
3057
3385
  const srcParts = srcResolved.split("/").filter(Boolean);
3058
3386
  const srcParentPath = "/" + srcParts.slice(0, -1).join("/");
3059
3387
  const srcParent = this.getNode(srcParentPath);
3060
3388
  if (!srcParent || !srcParent.children) {
3061
3389
  throw new Error(`mv: ${src}: No such file or directory`);
3062
3390
  }
3063
- const srcIndex = srcParent.children.findIndex(
3064
- (c) => c.name === srcNode.name
3065
- );
3391
+ this.assertModifyDir(srcParent, srcParentPath, "mv", src);
3392
+ if (destResolved === srcResolved || destNode && destNode.type === "directory" && destNode === srcParent) {
3393
+ throw new Error(`mv: '${src}' and '${dest}' are the same file`);
3394
+ }
3066
3395
  if (destNode && destNode.type === "directory") {
3396
+ this.assertModifyDir(destNode, destResolved, "mv", dest);
3067
3397
  if (!destNode.children) {
3068
3398
  destNode.children = [];
3069
3399
  }
@@ -3073,7 +3403,7 @@ var files = (() => {
3073
3403
  if (existingIdx !== -1) {
3074
3404
  destNode.children.splice(existingIdx, 1);
3075
3405
  }
3076
- srcParent.children.splice(srcIndex, 1);
3406
+ srcParent.children.splice(srcParent.children.indexOf(srcNode), 1);
3077
3407
  destNode.children.push(srcNode);
3078
3408
  destNode.modifiedAt = Date.now();
3079
3409
  } else {
@@ -3086,7 +3416,8 @@ var files = (() => {
3086
3416
  `mv: ${destParentPath}: No such file or directory`
3087
3417
  );
3088
3418
  }
3089
- srcParent.children.splice(srcIndex, 1);
3419
+ this.assertModifyDir(destParent, destParentPath, "mv", dest);
3420
+ srcParent.children.splice(srcParent.children.indexOf(srcNode), 1);
3090
3421
  srcNode.name = newName;
3091
3422
  if (!destParent.children) {
3092
3423
  destParent.children = [];
@@ -3109,6 +3440,9 @@ var files = (() => {
3109
3440
  if (!node) {
3110
3441
  throw new Error(`chmod: ${path}: No such file or directory`);
3111
3442
  }
3443
+ if (this.enforcing && !this.accessPolicy.canChangeOwnership(node)) {
3444
+ this.deny("chmod", path, "Operation not permitted");
3445
+ }
3112
3446
  node.permissions = permissions;
3113
3447
  }
3114
3448
  chown(path, ownership) {
@@ -3117,6 +3451,9 @@ var files = (() => {
3117
3451
  if (!node) {
3118
3452
  throw new Error(`chown: ${path}: No such file or directory`);
3119
3453
  }
3454
+ if (this.enforcing && !this.accessPolicy.canChangeOwnership(node, ownership)) {
3455
+ this.deny("chown", path, "Operation not permitted");
3456
+ }
3120
3457
  node.ownership = ownership;
3121
3458
  }
3122
3459
  // --- Persistence ---
@@ -3147,8 +3484,10 @@ var files = (() => {
3147
3484
  const tx = db.transaction(STORE_NAME, "readwrite");
3148
3485
  const store = tx.objectStore(STORE_NAME);
3149
3486
  store.put(this.root, ROOT_KEY);
3150
- store.put(this.cwd, CWD_KEY);
3151
- store.put(this.homePath, HOME_KEY);
3487
+ if (!this.currentUid) {
3488
+ store.put(this.cwd, CWD_KEY);
3489
+ store.put(this.homePath, HOME_KEY);
3490
+ }
3152
3491
  await new Promise((resolve, reject) => {
3153
3492
  tx.oncomplete = () => resolve();
3154
3493
  tx.onerror = () => reject(tx.error);
@@ -3158,6 +3497,18 @@ var files = (() => {
3158
3497
  }
3159
3498
  }
3160
3499
  // --- Private helpers ---
3500
+ /** A copy belongs to whoever made it (like `cp` without `-p`) */
3501
+ ownedByCurrentUser(node) {
3502
+ const owner = this.getDefaultOwnership();
3503
+ if (owner) {
3504
+ const apply = (n) => {
3505
+ n.ownership = { ...owner };
3506
+ n.children?.forEach(apply);
3507
+ };
3508
+ apply(node);
3509
+ }
3510
+ return node;
3511
+ }
3161
3512
  cloneNode(node) {
3162
3513
  const now = Date.now();
3163
3514
  const clone = {
@@ -3219,7 +3570,26 @@ var files = (() => {
3219
3570
  "find",
3220
3571
  "head",
3221
3572
  "tail",
3222
- "wc"
3573
+ "wc",
3574
+ "sort",
3575
+ "uniq",
3576
+ "cut",
3577
+ "paste",
3578
+ "tac",
3579
+ "diff",
3580
+ "sed",
3581
+ "awk",
3582
+ "du",
3583
+ "stat",
3584
+ "chmod",
3585
+ "chown",
3586
+ "ln",
3587
+ "tee",
3588
+ "basename",
3589
+ "dirname",
3590
+ "sh",
3591
+ "source",
3592
+ "xargs"
3223
3593
  ]);
3224
3594
  var FilePathCompletionProvider = class {
3225
3595
  constructor(fs) {
@@ -3238,7 +3608,14 @@ var files = (() => {
3238
3608
  if (token.startsWith("-")) {
3239
3609
  return [];
3240
3610
  }
3241
- return this.completePath(token);
3611
+ const quote = token.startsWith('"') || token.startsWith("'") ? token[0] : "";
3612
+ const partial = quote ? token.slice(1).replace(new RegExp(`${quote}$`), "") : token;
3613
+ const paths = this.completePath(partial);
3614
+ if (!quote && !paths.some((p) => /\s/.test(p))) {
3615
+ return paths;
3616
+ }
3617
+ const q = quote || '"';
3618
+ return paths.map((p) => p.endsWith("/") ? `${q}${p}` : `${q}${p}${q}`);
3242
3619
  }
3243
3620
  completePath(partial) {
3244
3621
  let dirPath;
@@ -3282,6 +3659,11 @@ var files = (() => {
3282
3659
  }
3283
3660
  };
3284
3661
 
3662
+ // src/lib/utils/split-args.ts
3663
+ function splitArgs(raw) {
3664
+ return splitShellWords(raw);
3665
+ }
3666
+
3285
3667
  // src/lib/processors/cli-ls-command-processor.ts
3286
3668
  var CliLsCommandProcessor = class {
3287
3669
  constructor() {
@@ -3305,41 +3687,63 @@ var files = (() => {
3305
3687
  description: "Use long listing format",
3306
3688
  required: false,
3307
3689
  type: "boolean"
3690
+ },
3691
+ {
3692
+ name: "human-readable",
3693
+ aliases: ["h"],
3694
+ description: "With -l, print sizes like 1.5K and 2M",
3695
+ required: false,
3696
+ type: "boolean"
3308
3697
  }
3309
3698
  ];
3699
+ this.humanReadable = false;
3310
3700
  }
3311
3701
  async processCommand(command, context) {
3312
- const fs = context.services.get(
3702
+ const fs = context.services.getRequired(
3313
3703
  IFileSystemService_TOKEN
3314
3704
  );
3315
- const targetPath = command.value || fs.getCurrentDirectory();
3705
+ const paths = splitArgs(command.value || "");
3316
3706
  const showAll = command.args["all"] || command.args["a"];
3317
3707
  const longFormat = command.args["long"] || command.args["l"];
3318
- try {
3319
- const entries = fs.listDirectory(targetPath);
3320
- const filtered = showAll ? entries : entries.filter((e) => !e.name.startsWith("."));
3321
- if (filtered.length === 0) {
3322
- return;
3323
- }
3324
- filtered.sort((a, b) => {
3325
- if (a.type !== b.type) {
3326
- return a.type === "directory" ? -1 : 1;
3708
+ this.humanReadable = !!(command.args["human-readable"] || command.args["h"]);
3709
+ const targets = paths.length > 0 ? paths : [fs.getCurrentDirectory()];
3710
+ const piped = [];
3711
+ targets.forEach((targetPath, index) => {
3712
+ try {
3713
+ const node = fs.getNode(targetPath);
3714
+ const entries = node && node.type !== "directory" ? [{ ...node, name: targetPath }] : fs.listDirectory(targetPath);
3715
+ const filtered = showAll ? entries : entries.filter((e) => !e.name.startsWith("."));
3716
+ if (targets.length > 1) {
3717
+ if (index > 0) context.writer.writeln();
3718
+ context.writer.writeln(`${targetPath}:`);
3327
3719
  }
3328
- return a.name.localeCompare(b.name);
3329
- });
3330
- if (longFormat) {
3331
- this.writeLongFormat(filtered, context);
3332
- } else {
3333
- this.writeShortFormat(filtered, context);
3720
+ if (filtered.length === 0) {
3721
+ return;
3722
+ }
3723
+ filtered.sort((a, b) => {
3724
+ if (a.type !== b.type) {
3725
+ return a.type === "directory" ? -1 : 1;
3726
+ }
3727
+ return a.name.localeCompare(b.name);
3728
+ });
3729
+ if (longFormat) {
3730
+ this.writeLongFormat(filtered, context);
3731
+ } else {
3732
+ this.writeShortFormat(filtered, context);
3733
+ piped.push(...filtered.map((e) => e.name));
3734
+ }
3735
+ } catch (e) {
3736
+ context.writer.writeError(e.message);
3334
3737
  }
3335
- } catch (e) {
3336
- context.writer.writeError(e.message);
3738
+ });
3739
+ if (!longFormat && piped.length > 0) {
3740
+ context.process.output(piped.join("\n"));
3337
3741
  }
3338
3742
  }
3339
3743
  writeDescription(context) {
3340
3744
  context.writer.writeln(this.description);
3341
3745
  context.writer.writeln();
3342
- context.writer.writeln("Usage: ls [path] [--all] [--long]");
3746
+ context.writer.writeln("Usage: ls [path...] [-a|--all] [-l|--long] [-h|--human-readable]");
3343
3747
  }
3344
3748
  writeLongFormat(entries, context) {
3345
3749
  const { writer } = context;
@@ -3348,7 +3752,7 @@ var files = (() => {
3348
3752
  const perms = entry.permissions || (entry.type === "directory" ? "rwxr-xr-x" : "rw-r--r--");
3349
3753
  const owner = entry.ownership?.uid || "-";
3350
3754
  const group = entry.ownership?.gid || "-";
3351
- const size = entry.type === "file" ? entry.size.toString() : "-";
3755
+ const size = entry.type !== "file" ? "-" : this.humanReadable ? this.formatSize(entry.size) : entry.size.toString();
3352
3756
  const date = new Date(entry.modifiedAt);
3353
3757
  const dateStr = date.toLocaleDateString("en-US", {
3354
3758
  month: "short",
@@ -3362,6 +3766,17 @@ var files = (() => {
3362
3766
  );
3363
3767
  }
3364
3768
  }
3769
+ formatSize(bytes) {
3770
+ const units = ["K", "M", "G", "T"];
3771
+ let size = bytes;
3772
+ let unit = "";
3773
+ for (const next of units) {
3774
+ if (size < 1024) break;
3775
+ size /= 1024;
3776
+ unit = next;
3777
+ }
3778
+ return unit ? `${size < 10 ? size.toFixed(1) : Math.round(size)}${unit}` : String(bytes);
3779
+ }
3365
3780
  writeShortFormat(entries, context) {
3366
3781
  const { writer } = context;
3367
3782
  const names = entries.map(
@@ -3381,11 +3796,30 @@ var files = (() => {
3381
3796
  this.acceptsRawInput = true;
3382
3797
  this.metadata = { icon: "\u{1F4C1}", module: "file management" };
3383
3798
  }
3799
+ writeDescription(context) {
3800
+ const t = context.translator;
3801
+ writeWrapped(
3802
+ context,
3803
+ t.t("cli.cd.help.change_current_directory_without", "Change the current directory. Without a path it goes to your home directory. Paths can be absolute (/...), relative to where you are, or start with ~ for home.")
3804
+ );
3805
+ context.writer.writeln();
3806
+ writeUsage(context, [
3807
+ ["cd", t.t("cli.cd.help.go_home_directory", "Go to your home directory")],
3808
+ ["cd <dir>", t.t("cli.cd.help.go_directory", "Go into a directory")],
3809
+ ["cd ..", t.t("cli.cd.help.go_up_one_level", "Go up one level")]
3810
+ ]);
3811
+ context.writer.writeln();
3812
+ writeExamples(context, [
3813
+ ["cd ~/docs", t.t("cli.cd.help.go_docs_home", "Go to docs in your home")],
3814
+ ["cd ../projects", t.t("cli.cd.help.go_sibling_directory", "Go to a sibling directory")],
3815
+ ["cd /", t.t("cli.cd.help.go_root", "Go to the root")]
3816
+ ]);
3817
+ }
3384
3818
  async processCommand(command, context) {
3385
- const fs = context.services.get(
3819
+ const fs = context.services.getRequired(
3386
3820
  IFileSystemService_TOKEN
3387
3821
  );
3388
- const target = command.value || "~";
3822
+ const target = splitArgs(command.value || "").join(" ") || "~";
3389
3823
  try {
3390
3824
  fs.setCurrentDirectory(target);
3391
3825
  await fs.persist();
@@ -3404,8 +3838,24 @@ var files = (() => {
3404
3838
  this.version = LIBRARY_VERSION;
3405
3839
  this.metadata = { icon: "\u{1F4C2}", module: "file management" };
3406
3840
  }
3841
+ writeDescription(context) {
3842
+ const t = context.translator;
3843
+ writeWrapped(
3844
+ context,
3845
+ t.t("cli.pwd.help.print_full_path_directory", "Print the full path of the directory you are in. Relative paths in other commands start from here.")
3846
+ );
3847
+ context.writer.writeln();
3848
+ writeUsage(context, [
3849
+ ["pwd", t.t("cli.pwd.help.show_current_directory", "Show the current directory")]
3850
+ ]);
3851
+ context.writer.writeln();
3852
+ writeExamples(context, [
3853
+ ["pwd", t.t("cli.pwd.help.example_home_user", "For example /home/user")],
3854
+ ["cd ~/docs; pwd", t.t("cli.pwd.help.prints_home_user_docs", "Prints /home/user/docs")]
3855
+ ]);
3856
+ }
3407
3857
  async processCommand(command, context) {
3408
- const fs = context.services.get(
3858
+ const fs = context.services.getRequired(
3409
3859
  IFileSystemService_TOKEN
3410
3860
  );
3411
3861
  context.writer.writeln(fs.getCurrentDirectory());
@@ -3432,22 +3882,44 @@ var files = (() => {
3432
3882
  }
3433
3883
  ];
3434
3884
  }
3885
+ writeDescription(context) {
3886
+ const t = context.translator;
3887
+ writeWrapped(
3888
+ context,
3889
+ t.t("cli.mkdir.help.create_new_directory_p", "Create a new directory. With -p, any missing parent directories are created too.")
3890
+ );
3891
+ context.writer.writeln();
3892
+ writeUsage(context, [
3893
+ ["mkdir <dir...>", t.t("cli.mkdir.help.create_one_more_directories", "Create one or more directories")],
3894
+ ["mkdir -p <path>", t.t("cli.mkdir.help.create_directory_missing_parents", "Create a directory and its missing parents")]
3895
+ ]);
3896
+ context.writer.writeln();
3897
+ writeExamples(context, [
3898
+ ["mkdir projects", t.t("cli.mkdir.help.create_projects_current_directory", "Create projects in the current directory")],
3899
+ ["mkdir -p ~/docs/2026/notes", t.t("cli.mkdir.help.create_whole_path_once", "Create the whole path at once")]
3900
+ ]);
3901
+ }
3435
3902
  async processCommand(command, context) {
3436
- const fs = context.services.get(
3903
+ const fs = context.services.getRequired(
3437
3904
  IFileSystemService_TOKEN
3438
3905
  );
3439
- const path = command.value;
3906
+ const paths = splitArgs(command.value || "");
3440
3907
  const recursive = command.args["parents"] || command.args["p"];
3441
- if (!path) {
3908
+ if (paths.length === 0) {
3442
3909
  context.writer.writeError("mkdir: missing operand");
3443
3910
  return;
3444
3911
  }
3445
- try {
3446
- fs.createDirectory(path, recursive);
3447
- await fs.persist();
3448
- } catch (e) {
3449
- context.writer.writeError(e.message);
3912
+ let changed = false;
3913
+ for (const path of paths) {
3914
+ try {
3915
+ if (recursive && fs.isDirectory(path)) continue;
3916
+ fs.createDirectory(path, recursive);
3917
+ changed = true;
3918
+ } catch (e) {
3919
+ context.writer.writeError(e.message);
3920
+ }
3450
3921
  }
3922
+ if (changed) await fs.persist();
3451
3923
  }
3452
3924
  };
3453
3925
 
@@ -3462,38 +3934,58 @@ var files = (() => {
3462
3934
  this.valueRequired = true;
3463
3935
  this.metadata = { icon: "\u{1F5D1}", module: "file management" };
3464
3936
  }
3937
+ writeDescription(context) {
3938
+ const t = context.translator;
3939
+ writeWrapped(
3940
+ context,
3941
+ t.t("cli.rmdir.help.remove_empty_directory_refuses", "Remove an empty directory. It refuses to remove a directory that still contains files; use rm -r for that.")
3942
+ );
3943
+ context.writer.writeln();
3944
+ writeUsage(context, [
3945
+ ["rmdir <dir...>", t.t("cli.rmdir.help.remove_empty_directories", "Remove empty directories")]
3946
+ ]);
3947
+ context.writer.writeln();
3948
+ writeExamples(context, [
3949
+ ["rmdir empty-folder", t.t("cli.rmdir.help.remove_directory_current_one", "Remove a directory in the current one")],
3950
+ ["rmdir ~/docs/old", t.t("cli.rmdir.help.remove_full_path", "Remove by full path")]
3951
+ ]);
3952
+ }
3465
3953
  async processCommand(command, context) {
3466
- const fs = context.services.get(
3954
+ const fs = context.services.getRequired(
3467
3955
  IFileSystemService_TOKEN
3468
3956
  );
3469
- const path = command.value;
3470
- if (!path) {
3957
+ const paths = splitArgs(command.value || "");
3958
+ if (paths.length === 0) {
3471
3959
  context.writer.writeError("rmdir: missing operand");
3472
3960
  return;
3473
3961
  }
3474
- try {
3475
- const node = fs.getNode(path);
3476
- if (!node) {
3477
- context.writer.writeError(
3478
- `rmdir: ${path}: No such file or directory`
3479
- );
3480
- return;
3481
- }
3482
- if (node.type !== "directory") {
3483
- context.writer.writeError(`rmdir: ${path}: Not a directory`);
3484
- return;
3485
- }
3486
- if (node.children && node.children.length > 0) {
3487
- context.writer.writeError(
3488
- `rmdir: ${path}: Directory not empty`
3489
- );
3490
- return;
3962
+ let changed = false;
3963
+ for (const path of paths) {
3964
+ try {
3965
+ const node = fs.getNode(path);
3966
+ if (!node) {
3967
+ context.writer.writeError(
3968
+ `rmdir: ${path}: No such file or directory`
3969
+ );
3970
+ continue;
3971
+ }
3972
+ if (node.type !== "directory") {
3973
+ context.writer.writeError(`rmdir: ${path}: Not a directory`);
3974
+ continue;
3975
+ }
3976
+ if (node.children && node.children.length > 0) {
3977
+ context.writer.writeError(
3978
+ `rmdir: ${path}: Directory not empty`
3979
+ );
3980
+ continue;
3981
+ }
3982
+ fs.remove(path, true);
3983
+ changed = true;
3984
+ } catch (e) {
3985
+ context.writer.writeError(e.message);
3491
3986
  }
3492
- fs.remove(path);
3493
- await fs.persist();
3494
- } catch (e) {
3495
- context.writer.writeError(e.message);
3496
3987
  }
3988
+ if (changed) await fs.persist();
3497
3989
  }
3498
3990
  };
3499
3991
 
@@ -3508,24 +4000,55 @@ var files = (() => {
3508
4000
  this.valueRequired = true;
3509
4001
  this.metadata = { icon: "\u{1F4C4}", module: "file management" };
3510
4002
  }
4003
+ writeDescription(context) {
4004
+ const t = context.translator;
4005
+ writeWrapped(
4006
+ context,
4007
+ t.t("cli.touch.help.create_empty_file_update", "Create an empty file, or update the modified time of a file that already exists. The parent directory must already exist.")
4008
+ );
4009
+ context.writer.writeln();
4010
+ writeUsage(context, [
4011
+ ["touch <file...>", t.t("cli.touch.help.create_files_refresh_their", "Create the files, or refresh their timestamps")]
4012
+ ]);
4013
+ context.writer.writeln();
4014
+ writeExamples(context, [
4015
+ ["touch notes.txt", t.t("cli.touch.help.create_empty_notes_txt", "Create an empty notes.txt")],
4016
+ ["touch ~/docs/todo.md", t.t("cli.touch.help.create_file_docs", "Create a file in ~/docs")]
4017
+ ]);
4018
+ }
3511
4019
  async processCommand(command, context) {
3512
- const fs = context.services.get(
4020
+ const fs = context.services.getRequired(
3513
4021
  IFileSystemService_TOKEN
3514
4022
  );
3515
- const path = command.value;
3516
- if (!path) {
4023
+ const paths = splitArgs(command.value || "");
4024
+ if (paths.length === 0) {
3517
4025
  context.writer.writeError("touch: missing file operand");
3518
4026
  return;
3519
4027
  }
3520
- try {
3521
- fs.createFile(path);
3522
- await fs.persist();
3523
- } catch (e) {
3524
- context.writer.writeError(e.message);
4028
+ let changed = false;
4029
+ for (const path of paths) {
4030
+ try {
4031
+ fs.createFile(path);
4032
+ changed = true;
4033
+ } catch (e) {
4034
+ context.writer.writeError(e.message);
4035
+ }
3525
4036
  }
4037
+ if (changed) await fs.persist();
3526
4038
  }
3527
4039
  };
3528
4040
 
4041
+ // src/lib/utils/lines.ts
4042
+ function splitLines(content) {
4043
+ if (content === "") return [];
4044
+ const lines = content.split("\n");
4045
+ if (lines[lines.length - 1] === "") lines.pop();
4046
+ return lines;
4047
+ }
4048
+ function pipedText(data) {
4049
+ return typeof data === "string" ? data : JSON.stringify(data);
4050
+ }
4051
+
3529
4052
  // src/lib/processors/cli-cat-command-processor.ts
3530
4053
  var CliCatCommandProcessor = class {
3531
4054
  constructor() {
@@ -3537,22 +4060,58 @@ var files = (() => {
3537
4060
  this.valueRequired = true;
3538
4061
  this.metadata = { icon: "\u{1F4D6}", module: "file management" };
3539
4062
  }
4063
+ writeDescription(context) {
4064
+ const t = context.translator;
4065
+ writeWrapped(
4066
+ context,
4067
+ t.t("cli.cat.help.print_contents_one_more", "Print the contents of one or more files, joined in order, or of piped input. Pipe the output into other commands to search, sort or count it.")
4068
+ );
4069
+ context.writer.writeln();
4070
+ writeUsage(context, [
4071
+ ["cat <file...>", t.t("cli.cat.help.print_files_one_after", "Print files one after another")],
4072
+ ["cat <file> | <command>", t.t("cli.cat.help.send_file_another_command", "Send a file to another command")]
4073
+ ]);
4074
+ context.writer.writeln();
4075
+ writeExamples(context, [
4076
+ ["cat notes.txt", t.t("cli.cat.help.show_whole_file", "Show the whole file")],
4077
+ ["cat app.log | grep error", t.t("cli.cat.help.show_only_lines_error", "Show only lines with error")],
4078
+ ["cat names.txt | sort", t.t("cli.cat.help.print_lines_sorted", "Print the lines sorted")]
4079
+ ]);
4080
+ }
3540
4081
  async processCommand(command, context) {
3541
- const fs = context.services.get(
4082
+ const fs = context.services.getRequired(
3542
4083
  IFileSystemService_TOKEN
3543
4084
  );
3544
- const path = command.value;
3545
- if (!path) {
4085
+ const paths = splitArgs(command.value || "");
4086
+ if (paths.length === 0) {
4087
+ if (command.data != null) {
4088
+ context.writer.writeln(pipedText(command.data));
4089
+ return;
4090
+ }
3546
4091
  context.writer.writeError("cat: missing file operand");
3547
4092
  return;
3548
4093
  }
3549
- try {
3550
- const content = fs.readFile(path);
3551
- if (content !== null) {
3552
- context.writer.writeln(content);
4094
+ let output = "";
4095
+ for (const path of paths) {
4096
+ try {
4097
+ if (fs.isDirectory(path)) {
4098
+ context.writer.writeError(`cat: ${path}: Is a directory`);
4099
+ continue;
4100
+ }
4101
+ const content = fs.readFile(path);
4102
+ if (content === null) {
4103
+ context.writer.writeError(
4104
+ `cat: ${path}: No such file or directory`
4105
+ );
4106
+ continue;
4107
+ }
4108
+ output += content;
4109
+ } catch (e) {
4110
+ context.writer.writeError(e.message);
3553
4111
  }
3554
- } catch (e) {
3555
- context.writer.writeError(e.message);
4112
+ }
4113
+ if (output !== "") {
4114
+ context.writer.writeln(output.replace(/\n$/, ""));
3556
4115
  }
3557
4116
  }
3558
4117
  };
@@ -3568,60 +4127,33 @@ var files = (() => {
3568
4127
  this.extendsProcessor = true;
3569
4128
  this.metadata = { icon: "\u{1F4AC}", module: "file management" };
3570
4129
  }
4130
+ writeDescription(context) {
4131
+ const t = context.translator;
4132
+ writeWrapped(
4133
+ context,
4134
+ t.t("cli.echo.help.print_text_terminal_add", "Print text to the terminal. Add > file to save it to a file (replacing it) or >> file to add it to the end.")
4135
+ );
4136
+ context.writer.writeln();
4137
+ writeUsage(context, [
4138
+ ["echo <text>", t.t("cli.echo.help.print_text", "Print text")],
4139
+ ["echo <text> > <file>", t.t("cli.echo.help.write_text_file", "Write text to a file")],
4140
+ ["echo <text> >> <file>", t.t("cli.echo.help.append_text_file", "Append text to a file")]
4141
+ ]);
4142
+ context.writer.writeln();
4143
+ writeExamples(context, [
4144
+ ["echo Hello world", t.t("cli.echo.help.print_greeting", "Print a greeting")],
4145
+ ["echo first line > notes.txt", t.t("cli.echo.help.create_replace_notes_txt", "Create or replace notes.txt")],
4146
+ ["echo another line >> notes.txt", t.t("cli.echo.help.add_line", "Add a line to it")]
4147
+ ]);
4148
+ }
3571
4149
  async processCommand(command, context) {
3572
- const afterEcho = command.value || "";
3573
- const hasRedirection = />>?\s*.+$/.test(afterEcho);
3574
- if (!hasRedirection) {
3575
- if (this.originalProcessor) {
3576
- await this.originalProcessor.processCommand(command, context);
3577
- } else {
3578
- context.writer.writeln(afterEcho);
3579
- }
4150
+ if (this.originalProcessor) {
4151
+ await this.originalProcessor.processCommand(command, context);
3580
4152
  return;
3581
4153
  }
3582
- const fs = context.services.get(
3583
- IFileSystemService_TOKEN
3584
- );
3585
- let text;
3586
- let filePath;
3587
- let append = false;
3588
- const appendMatch = afterEcho.match(/^(.*?)\s*>>\s*(.+)$/);
3589
- const overwriteMatch = afterEcho.match(/^(.*?)\s*>\s*(.+)$/);
3590
- if (appendMatch) {
3591
- text = appendMatch[1].trim();
3592
- filePath = appendMatch[2].trim();
3593
- append = true;
3594
- } else {
3595
- text = overwriteMatch[1].trim();
3596
- filePath = overwriteMatch[2].trim();
3597
- }
3598
- if (text.startsWith('"') && text.endsWith('"') || text.startsWith("'") && text.endsWith("'")) {
3599
- text = text.slice(1, -1);
3600
- }
3601
- text = text.replace(/\\([\\nrtv0])/g, (_, ch) => {
3602
- switch (ch) {
3603
- case "n":
3604
- return "\n";
3605
- case "t":
3606
- return " ";
3607
- case "r":
3608
- return "\r";
3609
- case "v":
3610
- return "\v";
3611
- case "0":
3612
- return "\0";
3613
- case "\\":
3614
- return "\\";
3615
- default:
3616
- return ch;
3617
- }
3618
- });
3619
- try {
3620
- fs.writeFile(filePath, text + "\n", append);
3621
- await fs.persist();
3622
- } catch (e) {
3623
- context.writer.writeError(e.message);
3624
- }
4154
+ const text = joinShellWords(command.value || "");
4155
+ context.writer.writeln(text);
4156
+ context.process.output(text);
3625
4157
  }
3626
4158
  };
3627
4159
 
@@ -3652,35 +4184,58 @@ var files = (() => {
3652
4184
  }
3653
4185
  ];
3654
4186
  }
4187
+ writeDescription(context) {
4188
+ const t = context.translator;
4189
+ writeWrapped(
4190
+ context,
4191
+ t.t("cli.rm.help.delete_file_directories_only", "Delete a file. Directories are only removed with -r, which deletes everything inside them. Deleted files cannot be recovered.")
4192
+ );
4193
+ context.writer.writeln();
4194
+ writeUsage(context, [
4195
+ ["rm <file...>", t.t("cli.rm.help.delete_one_more_files", "Delete one or more files")],
4196
+ ["rm -r <dir>", t.t("cli.rm.help.delete_directory_contents", "Delete a directory and its contents")],
4197
+ ["rm -f <file>", t.t("cli.rm.help.delete_without_error_if", "Delete without an error if it is missing")]
4198
+ ]);
4199
+ context.writer.writeln();
4200
+ writeExamples(context, [
4201
+ ["rm notes.bak", t.t("cli.rm.help.delete_one_file", "Delete one file")],
4202
+ ["rm -r old-project", t.t("cli.rm.help.delete_whole_directory", "Delete a whole directory")],
4203
+ ["rm -f maybe.txt", t.t("cli.rm.help.quiet_if_file_does", "Quiet if the file does not exist")]
4204
+ ]);
4205
+ }
3655
4206
  async processCommand(command, context) {
3656
- const fs = context.services.get(
4207
+ const fs = context.services.getRequired(
3657
4208
  IFileSystemService_TOKEN
3658
4209
  );
3659
- const path = command.value;
4210
+ const paths = splitArgs(command.value || "");
3660
4211
  const recursive = command.args["recursive"] || command.args["r"] || command.args["R"];
3661
4212
  const force = command.args["force"] || command.args["f"];
3662
- if (!path) {
3663
- context.writer.writeError("rm: missing operand");
4213
+ if (paths.length === 0) {
4214
+ if (!force) context.writer.writeError("rm: missing operand");
3664
4215
  return;
3665
4216
  }
3666
- try {
3667
- if (!fs.exists(path)) {
3668
- if (!force) {
3669
- context.writer.writeError(
3670
- `rm: ${path}: No such file or directory`
3671
- );
4217
+ let changed = false;
4218
+ for (const path of paths) {
4219
+ try {
4220
+ if (!fs.exists(path)) {
4221
+ if (!force) {
4222
+ context.writer.writeError(
4223
+ `rm: ${path}: No such file or directory`
4224
+ );
4225
+ }
4226
+ continue;
3672
4227
  }
3673
- return;
3674
- }
3675
- if (fs.isDirectory(path) && !recursive) {
3676
- context.writer.writeError(`rm: ${path}: Is a directory`);
3677
- return;
4228
+ if (fs.isDirectory(path) && !recursive) {
4229
+ context.writer.writeError(`rm: ${path}: Is a directory`);
4230
+ continue;
4231
+ }
4232
+ fs.remove(path, recursive);
4233
+ changed = true;
4234
+ } catch (e) {
4235
+ context.writer.writeError(e.message);
3678
4236
  }
3679
- fs.remove(path, recursive);
3680
- await fs.persist();
3681
- } catch (e) {
3682
- context.writer.writeError(e.message);
3683
4237
  }
4238
+ if (changed) await fs.persist();
3684
4239
  }
3685
4240
  };
3686
4241
 
@@ -3700,11 +4255,37 @@ var files = (() => {
3700
4255
  description: "Copy directories recursively",
3701
4256
  required: false,
3702
4257
  type: "boolean"
4258
+ },
4259
+ {
4260
+ name: "force",
4261
+ aliases: ["f"],
4262
+ description: "Overwrite existing files (always done; accepted for -rf)",
4263
+ required: false,
4264
+ type: "boolean"
3703
4265
  }
3704
4266
  ];
3705
4267
  }
4268
+ writeDescription(context) {
4269
+ const t = context.translator;
4270
+ writeWrapped(
4271
+ context,
4272
+ t.t("cli.cp.help.copy_file_new_path", "Copy a file to a new path, or into an existing directory. Copying a directory needs -r, which copies everything inside it. A file already at the destination is replaced.")
4273
+ );
4274
+ context.writer.writeln();
4275
+ writeUsage(context, [
4276
+ ["cp <source> <dest>", t.t("cli.cp.help.copy_file_new_name", "Copy a file to a new name")],
4277
+ ["cp <source...> <dir>", t.t("cli.cp.help.copy_files_directory", "Copy files into a directory")],
4278
+ ["cp -r <dir> <dest>", t.t("cli.cp.help.copy_directory_contents", "Copy a directory and its contents")]
4279
+ ]);
4280
+ context.writer.writeln();
4281
+ writeExamples(context, [
4282
+ ["cp notes.txt notes.bak", t.t("cli.cp.help.make_backup_copy", "Make a backup copy")],
4283
+ ["cp report.txt ~/docs", t.t("cli.cp.help.copy_docs", "Copy into ~/docs")],
4284
+ ["cp -r project project-copy", t.t("cli.cp.help.duplicate_whole_directory", "Duplicate a whole directory")]
4285
+ ]);
4286
+ }
3706
4287
  async processCommand(command, context) {
3707
- const fs = context.services.get(
4288
+ const fs = context.services.getRequired(
3708
4289
  IFileSystemService_TOKEN
3709
4290
  );
3710
4291
  const recursive = command.args["recursive"] || command.args["r"] || command.args["R"];
@@ -3716,18 +4297,26 @@ var files = (() => {
3716
4297
  );
3717
4298
  return;
3718
4299
  }
3719
- const src = parts[0];
3720
- const dest = parts[1];
3721
- try {
3722
- fs.copy(src, dest, recursive);
3723
- await fs.persist();
3724
- } catch (e) {
3725
- context.writer.writeError(e.message);
4300
+ const dest = parts[parts.length - 1];
4301
+ const sources = parts.slice(0, -1);
4302
+ if (sources.length > 1 && !fs.isDirectory(dest)) {
4303
+ context.writer.writeError(`cp: target '${dest}' is not a directory`);
4304
+ return;
3726
4305
  }
4306
+ let changed = false;
4307
+ for (const src of sources) {
4308
+ try {
4309
+ fs.copy(src, dest, recursive);
4310
+ changed = true;
4311
+ } catch (e) {
4312
+ context.writer.writeError(e.message);
4313
+ }
4314
+ }
4315
+ if (changed) await fs.persist();
3727
4316
  }
3728
4317
  parseArgs(command) {
3729
4318
  const raw = command.value || "";
3730
- return raw.split(/\s+/).filter((t) => t && !t.startsWith("--") && !t.startsWith("-"));
4319
+ return splitArgs(raw).filter(Boolean);
3731
4320
  }
3732
4321
  };
3733
4322
 
@@ -3741,8 +4330,26 @@ var files = (() => {
3741
4330
  this.acceptsRawInput = true;
3742
4331
  this.metadata = { icon: "\u{1F4E6}", module: "file management" };
3743
4332
  }
4333
+ writeDescription(context) {
4334
+ const t = context.translator;
4335
+ writeWrapped(
4336
+ context,
4337
+ t.t("cli.mv.help.move_rename_file_directory", "Move or rename a file or directory. If the destination is an existing directory, the source is moved into it; otherwise it is renamed. A file already at the destination is replaced.")
4338
+ );
4339
+ context.writer.writeln();
4340
+ writeUsage(context, [
4341
+ ["mv <source> <dest>", t.t("cli.mv.help.rename_file_directory", "Rename a file or directory")],
4342
+ ["mv <source...> <dir>", t.t("cli.mv.help.move_them_directory", "Move them into a directory")]
4343
+ ]);
4344
+ context.writer.writeln();
4345
+ writeExamples(context, [
4346
+ ["mv draft.txt final.txt", t.t("cli.mv.help.rename_file", "Rename a file")],
4347
+ ["mv final.txt ~/docs", t.t("cli.mv.help.move_docs", "Move it into ~/docs")],
4348
+ ["mv old-dir new-dir", t.t("cli.mv.help.rename_directory", "Rename a directory")]
4349
+ ]);
4350
+ }
3744
4351
  async processCommand(command, context) {
3745
- const fs = context.services.get(
4352
+ const fs = context.services.getRequired(
3746
4353
  IFileSystemService_TOKEN
3747
4354
  );
3748
4355
  const parts = this.parseArgs(command);
@@ -3751,21 +4358,39 @@ var files = (() => {
3751
4358
  context.writer.writeln("Usage: mv <source> <destination>");
3752
4359
  return;
3753
4360
  }
3754
- const src = parts[0];
3755
- const dest = parts[1];
3756
- try {
3757
- fs.move(src, dest);
3758
- await fs.persist();
3759
- } catch (e) {
3760
- context.writer.writeError(e.message);
4361
+ const dest = parts[parts.length - 1];
4362
+ const sources = parts.slice(0, -1);
4363
+ if (sources.length > 1 && !fs.isDirectory(dest)) {
4364
+ context.writer.writeError(`mv: target '${dest}' is not a directory`);
4365
+ return;
3761
4366
  }
4367
+ let changed = false;
4368
+ for (const src of sources) {
4369
+ try {
4370
+ fs.move(src, dest);
4371
+ changed = true;
4372
+ } catch (e) {
4373
+ context.writer.writeError(e.message);
4374
+ }
4375
+ }
4376
+ if (changed) await fs.persist();
3762
4377
  }
3763
4378
  parseArgs(command) {
3764
4379
  const raw = command.value || "";
3765
- return raw.split(/\s+/).filter((t) => t && !t.startsWith("--") && !t.startsWith("-"));
4380
+ return splitArgs(raw).filter(Boolean);
3766
4381
  }
3767
4382
  };
3768
4383
 
4384
+ // src/lib/utils/readable-children.ts
4385
+ function readableChildren(fs, path) {
4386
+ try {
4387
+ return fs.listDirectory(path);
4388
+ } catch (e) {
4389
+ if (isCliAccessDeniedError(e)) return null;
4390
+ throw e;
4391
+ }
4392
+ }
4393
+
3769
4394
  // src/lib/processors/cli-tree-command-processor.ts
3770
4395
  var CliTreeCommandProcessor = class {
3771
4396
  constructor() {
@@ -3785,11 +4410,29 @@ var files = (() => {
3785
4410
  }
3786
4411
  ];
3787
4412
  }
4413
+ writeDescription(context) {
4414
+ const t = context.translator;
4415
+ writeWrapped(
4416
+ context,
4417
+ t.t("cli.tree.help.draw_directory_everything_below", "Draw a directory and everything below it as a tree, directories first, followed by a count of directories and files. Defaults to the current directory.")
4418
+ );
4419
+ context.writer.writeln();
4420
+ writeUsage(context, [
4421
+ ["tree [dir]", t.t("cli.tree.help.show_whole_tree", "Show the whole tree")],
4422
+ ["tree -L <n> [dir]", t.t("cli.tree.help.only_levels_deep", "Only <n> levels deep")]
4423
+ ]);
4424
+ context.writer.writeln();
4425
+ writeExamples(context, [
4426
+ ["tree", t.t("cli.tree.help.tree_current_directory", "Tree of the current directory")],
4427
+ ["tree ~/docs", t.t("cli.tree.help.tree_docs", "Tree of ~/docs")],
4428
+ ["tree -L 2", t.t("cli.tree.help.top_two_levels_only", "Top two levels only")]
4429
+ ]);
4430
+ }
3788
4431
  async processCommand(command, context) {
3789
- const fs = context.services.get(
4432
+ const fs = context.services.getRequired(
3790
4433
  IFileSystemService_TOKEN
3791
4434
  );
3792
- const targetPath = command.value || fs.getCurrentDirectory();
4435
+ const targetPath = splitArgs(command.value || "").join(" ") || fs.getCurrentDirectory();
3793
4436
  const maxDepth = command.args["depth"] ? parseInt(command.args["depth"]) : command.args["L"] ? parseInt(command.args["L"]) : Infinity;
3794
4437
  try {
3795
4438
  const node = fs.getNode(targetPath);
@@ -3813,7 +4456,8 @@ var files = (() => {
3813
4456
  )
3814
4457
  );
3815
4458
  const counts = { dirs: 0, files: 0 };
3816
- this.printTree(node, "", true, 0, maxDepth, context, counts);
4459
+ this.fs = fs;
4460
+ this.printTree(node, "", true, 0, maxDepth, context, counts, resolvedPath);
3817
4461
  context.writer.writeln();
3818
4462
  context.writer.writeln(
3819
4463
  `${counts.dirs} directories, ${counts.files} files`
@@ -3822,11 +4466,15 @@ var files = (() => {
3822
4466
  context.writer.writeError(e.message);
3823
4467
  }
3824
4468
  }
3825
- printTree(node, prefix, isRoot, depth, maxDepth, context, counts) {
3826
- if (!node.children || depth >= maxDepth) {
4469
+ printTree(node, prefix, isRoot, depth, maxDepth, context, counts, path = "/") {
4470
+ if (depth >= maxDepth) {
4471
+ return;
4472
+ }
4473
+ const visible = this.fs ? readableChildren(this.fs, path) : node.children;
4474
+ if (!visible) {
3827
4475
  return;
3828
4476
  }
3829
- const children = [...node.children].sort((a, b) => {
4477
+ const children = [...visible].sort((a, b) => {
3830
4478
  if (a.type !== b.type) {
3831
4479
  return a.type === "directory" ? -1 : 1;
3832
4480
  }
@@ -3851,7 +4499,8 @@ var files = (() => {
3851
4499
  depth + 1,
3852
4500
  maxDepth,
3853
4501
  context,
3854
- counts
4502
+ counts,
4503
+ path === "/" ? `/${child.name}` : `${path}/${child.name}`
3855
4504
  );
3856
4505
  } else {
3857
4506
  counts.files++;
@@ -3880,21 +4529,40 @@ var files = (() => {
3880
4529
  }
3881
4530
  ];
3882
4531
  }
4532
+ writeDescription(context) {
4533
+ const t = context.translator;
4534
+ writeWrapped(
4535
+ context,
4536
+ t.t("cli.head.help.print_first_lines_file", "Print the first lines of a file, 10 by default. Works on piped input too. With several files, each one gets a ==> name <== header.")
4537
+ );
4538
+ context.writer.writeln();
4539
+ writeUsage(context, [
4540
+ ["head <file>", t.t("cli.head.help.first_10_lines", "First 10 lines")],
4541
+ ["head -n <count> <file>", t.t("cli.head.help.first_lines", "First <count> lines")],
4542
+ ["<command> | head", t.t("cli.head.help.first_lines_piped_input", "First lines of piped input")]
4543
+ ]);
4544
+ context.writer.writeln();
4545
+ writeExamples(context, [
4546
+ ["head notes.txt", t.t("cli.head.help.show_start_file", "Show the start of a file")],
4547
+ ["head -n 3 a.txt b.txt", t.t("cli.head.help.first_3_lines_each", "First 3 lines of each file")],
4548
+ ["cat app.log | head -n 20", t.t("cli.head.help.first_20_lines_log", "First 20 lines of the log")]
4549
+ ]);
4550
+ }
3883
4551
  async processCommand(command, context) {
3884
- const fs = context.services.get(
4552
+ const fs = context.services.getRequired(
3885
4553
  IFileSystemService_TOKEN
3886
4554
  );
3887
- const count = parseInt(
3888
- command.args["lines"] || command.args["n"] || "10",
3889
- 10
3890
- );
4555
+ const countArg = command.args["lines"] ?? command.args["n"] ?? Object.keys(command.args).find((k) => /^\d+$/.test(k));
4556
+ const parsedCount = parseInt(String(countArg ?? "10"), 10);
4557
+ const count = isNaN(parsedCount) ? 10 : Math.abs(parsedCount);
3891
4558
  const paths = this.parsePaths(command);
3892
4559
  if (paths.length === 0) {
3893
4560
  if (command.data != null) {
3894
- const content = typeof command.data === "string" ? command.data : JSON.stringify(command.data);
3895
- const lines = content.split("\n");
4561
+ const lines = splitLines(pipedText(command.data));
3896
4562
  const selected = lines.slice(0, count);
3897
- context.writer.writeln(selected.join("\n"));
4563
+ if (selected.length > 0) {
4564
+ context.writer.writeln(selected.join("\n"));
4565
+ }
3898
4566
  return;
3899
4567
  }
3900
4568
  context.writer.writeError("head: missing file operand");
@@ -3913,9 +4581,11 @@ var files = (() => {
3913
4581
  if (i > 0) context.writer.writeln();
3914
4582
  context.writer.writeln(`==> ${path} <==`);
3915
4583
  }
3916
- const lines = content.split("\n");
4584
+ const lines = splitLines(content);
3917
4585
  const selected = lines.slice(0, count);
3918
- context.writer.writeln(selected.join("\n"));
4586
+ if (selected.length > 0) {
4587
+ context.writer.writeln(selected.join("\n"));
4588
+ }
3919
4589
  } catch (e) {
3920
4590
  context.writer.writeError(`head: ${e.message}`);
3921
4591
  }
@@ -3923,7 +4593,7 @@ var files = (() => {
3923
4593
  }
3924
4594
  parsePaths(command) {
3925
4595
  const raw = command.value || "";
3926
- return raw.split(/\s+/).filter(Boolean);
4596
+ return splitArgs(raw).filter(Boolean);
3927
4597
  }
3928
4598
  };
3929
4599
 
@@ -3947,21 +4617,40 @@ var files = (() => {
3947
4617
  }
3948
4618
  ];
3949
4619
  }
4620
+ writeDescription(context) {
4621
+ const t = context.translator;
4622
+ writeWrapped(
4623
+ context,
4624
+ t.t("cli.tail.help.print_last_lines_file", "Print the last lines of a file, 10 by default. Works on piped input too. With several files, each one gets a ==> name <== header.")
4625
+ );
4626
+ context.writer.writeln();
4627
+ writeUsage(context, [
4628
+ ["tail <file>", t.t("cli.tail.help.last_10_lines", "Last 10 lines")],
4629
+ ["tail -n <count> <file>", t.t("cli.tail.help.last_lines", "Last <count> lines")],
4630
+ ["<command> | tail", t.t("cli.tail.help.last_lines_piped_input", "Last lines of piped input")]
4631
+ ]);
4632
+ context.writer.writeln();
4633
+ writeExamples(context, [
4634
+ ["tail app.log", t.t("cli.tail.help.show_end_log", "Show the end of a log")],
4635
+ ["tail -n 5 notes.txt", t.t("cli.tail.help.last_5_lines", "Last 5 lines")],
4636
+ ["cat app.log | tail -n 20", t.t("cli.tail.help.last_20_lines_log", "Last 20 lines of the log")]
4637
+ ]);
4638
+ }
3950
4639
  async processCommand(command, context) {
3951
- const fs = context.services.get(
4640
+ const fs = context.services.getRequired(
3952
4641
  IFileSystemService_TOKEN
3953
4642
  );
3954
- const count = parseInt(
3955
- command.args["lines"] || command.args["n"] || "10",
3956
- 10
3957
- );
4643
+ const countArg = command.args["lines"] ?? command.args["n"] ?? Object.keys(command.args).find((k) => /^\d+$/.test(k));
4644
+ const parsedCount = parseInt(String(countArg ?? "10"), 10);
4645
+ const count = isNaN(parsedCount) ? 10 : Math.abs(parsedCount);
3958
4646
  const paths = this.parsePaths(command);
3959
4647
  if (paths.length === 0) {
3960
4648
  if (command.data != null) {
3961
- const content = typeof command.data === "string" ? command.data : JSON.stringify(command.data);
3962
- const lines = content.split("\n");
3963
- const selected = lines.slice(-count);
3964
- context.writer.writeln(selected.join("\n"));
4649
+ const lines = splitLines(pipedText(command.data));
4650
+ const selected = count > 0 ? lines.slice(-count) : [];
4651
+ if (selected.length > 0) {
4652
+ context.writer.writeln(selected.join("\n"));
4653
+ }
3965
4654
  return;
3966
4655
  }
3967
4656
  context.writer.writeError("tail: missing file operand");
@@ -3980,9 +4669,11 @@ var files = (() => {
3980
4669
  if (i > 0) context.writer.writeln();
3981
4670
  context.writer.writeln(`==> ${path} <==`);
3982
4671
  }
3983
- const lines = content.split("\n");
3984
- const selected = lines.slice(-count);
3985
- context.writer.writeln(selected.join("\n"));
4672
+ const lines = splitLines(content);
4673
+ const selected = count > 0 ? lines.slice(-count) : [];
4674
+ if (selected.length > 0) {
4675
+ context.writer.writeln(selected.join("\n"));
4676
+ }
3986
4677
  } catch (e) {
3987
4678
  context.writer.writeError(`tail: ${e.message}`);
3988
4679
  }
@@ -3990,7 +4681,7 @@ var files = (() => {
3990
4681
  }
3991
4682
  parsePaths(command) {
3992
4683
  const raw = command.value || "";
3993
- return raw.split(/\s+/).filter(Boolean);
4684
+ return splitArgs(raw).filter(Boolean);
3994
4685
  }
3995
4686
  };
3996
4687
 
@@ -4028,8 +4719,30 @@ var files = (() => {
4028
4719
  }
4029
4720
  ];
4030
4721
  }
4722
+ writeDescription(context) {
4723
+ const t = context.translator;
4724
+ writeWrapped(
4725
+ context,
4726
+ t.t("cli.wc.help.count_lines_words_characters", "Count lines, words and characters in files or piped input. With several files, a total line is printed at the end.")
4727
+ );
4728
+ context.writer.writeln();
4729
+ writeUsage(context, [
4730
+ ["wc <file...>", t.t("cli.wc.help.lines_words_characters", "Lines, words and characters")],
4731
+ ["wc -l <file>", t.t("cli.wc.help.only_line_count", "Only the line count")],
4732
+ ["<command> | wc", t.t("cli.wc.help.count_piped_input", "Count piped input")]
4733
+ ]);
4734
+ context.writer.writeln();
4735
+ writeExamples(context, [
4736
+ ["wc notes.txt", t.t("cli.wc.help.all_three_counts", "All three counts")],
4737
+ ["wc -l a.txt b.txt", t.t("cli.wc.help.line_counts_total", "Line counts with a total")],
4738
+ [
4739
+ "cat app.log | grep error | wc -l",
4740
+ t.t("cli.wc.help.how_many_lines_mention", "How many lines mention error")
4741
+ ]
4742
+ ]);
4743
+ }
4031
4744
  async processCommand(command, context) {
4032
- const fs = context.services.get(
4745
+ const fs = context.services.getRequired(
4033
4746
  IFileSystemService_TOKEN
4034
4747
  );
4035
4748
  const showLines = command.args["lines"] || command.args["l"] || false;
@@ -4039,10 +4752,9 @@ var files = (() => {
4039
4752
  const paths = this.parsePaths(command);
4040
4753
  if (paths.length === 0) {
4041
4754
  if (command.data != null) {
4042
- const content = typeof command.data === "string" ? command.data : JSON.stringify(command.data);
4043
- const lineCount = content === "" ? 0 : content.split("\n").length;
4044
- const wordCount = content.trim() === "" ? 0 : content.trim().split(/\s+/).length;
4045
- const charCount = content.length;
4755
+ let content = pipedText(command.data);
4756
+ if (content !== "" && !content.endsWith("\n")) content += "\n";
4757
+ const { lineCount, wordCount, charCount } = this.count(content);
4046
4758
  const parts = [];
4047
4759
  if (showAll || showLines) parts.push(String(lineCount).padStart(8));
4048
4760
  if (showAll || showWords) parts.push(String(wordCount).padStart(8));
@@ -4063,9 +4775,7 @@ var files = (() => {
4063
4775
  continue;
4064
4776
  }
4065
4777
  const content = fs.readFile(path) ?? "";
4066
- const lineCount = content === "" ? 0 : content.split("\n").length;
4067
- const wordCount = content.trim() === "" ? 0 : content.trim().split(/\s+/).length;
4068
- const charCount = content.length;
4778
+ const { lineCount, wordCount, charCount } = this.count(content);
4069
4779
  totalLines += lineCount;
4070
4780
  totalWords += wordCount;
4071
4781
  totalChars += charCount;
@@ -4088,9 +4798,17 @@ var files = (() => {
4088
4798
  context.writer.writeln(parts.join(""));
4089
4799
  }
4090
4800
  }
4801
+ /** Counts like Unix wc: lines are newline characters. */
4802
+ count(content) {
4803
+ return {
4804
+ lineCount: (content.match(/\n/g) ?? []).length,
4805
+ wordCount: content.trim() === "" ? 0 : content.trim().split(/\s+/).length,
4806
+ charCount: content.length
4807
+ };
4808
+ }
4091
4809
  parsePaths(command) {
4092
4810
  const raw = command.value || "";
4093
- return raw.split(/\s+/).filter(Boolean);
4811
+ return splitArgs(raw).filter(Boolean);
4094
4812
  }
4095
4813
  };
4096
4814
 
@@ -4106,7 +4824,13 @@ var files = (() => {
4106
4824
  this.parameters = [
4107
4825
  {
4108
4826
  name: "name",
4109
- description: "Match filename pattern (glob: * and ? supported)",
4827
+ description: "Match filename pattern (glob: * and ? supported), case-sensitive",
4828
+ required: false,
4829
+ type: "string"
4830
+ },
4831
+ {
4832
+ name: "iname",
4833
+ description: "Like --name, but case-insensitive",
4110
4834
  required: false,
4111
4835
  type: "string"
4112
4836
  },
@@ -4118,20 +4842,42 @@ var files = (() => {
4118
4842
  },
4119
4843
  {
4120
4844
  name: "maxdepth",
4121
- description: "Maximum directory depth to search",
4845
+ description: "Maximum depth: 0 is only the start path, 1 its direct children",
4122
4846
  required: false,
4123
4847
  type: "number"
4124
4848
  }
4125
4849
  ];
4126
4850
  }
4851
+ writeDescription(context) {
4852
+ const t = context.translator;
4853
+ writeWrapped(
4854
+ context,
4855
+ t.t("cli.find.help.list_files_directories_under", "List files and directories under a path (the current directory by default), searching all subdirectories. The start path itself is listed first. Filter by name with a glob pattern (* and ?; --name is case-sensitive, --iname is not) or by type.")
4856
+ );
4857
+ context.writer.writeln();
4858
+ writeUsage(context, [
4859
+ ["find [path]", t.t("cli.find.help.list_everything_under_path", "List everything under a path")],
4860
+ ["find [path] --name <pattern>", t.t("cli.find.help.match_names_against_glob", "Match names against a glob")],
4861
+ ["find [path] --iname <pattern>", t.t("cli.find.help.same_ignoring_case", "Same, ignoring case")],
4862
+ ["find [path] --type f|d", t.t("cli.find.help.only_files_f_directories", "Only files (f) or directories (d)")]
4863
+ ]);
4864
+ context.writer.writeln();
4865
+ writeExamples(context, [
4866
+ ["find ~/docs", t.t("cli.find.help.list_everything_docs", "List everything in ~/docs")],
4867
+ ['find . --name "*.txt"', t.t("cli.find.help.find_all_txt_files", "Find all .txt files")],
4868
+ ["find . --type d --maxdepth 1", t.t("cli.find.help.directories_near_top", "Directories near the top")],
4869
+ ['find . --name "*.tmp" | xargs rm', t.t("cli.find.help.delete_all_tmp_files", "Delete all .tmp files")]
4870
+ ]);
4871
+ }
4127
4872
  async processCommand(command, context) {
4128
- const fs = context.services.get(
4873
+ const fs = context.services.getRequired(
4129
4874
  IFileSystemService_TOKEN
4130
4875
  );
4131
4876
  const searchPath = this.parseSearchPath(command) || fs.getCurrentDirectory();
4132
- const namePattern = command.args["name"] || null;
4133
4877
  const typeFilter = command.args["type"] || null;
4134
- const maxDepth = command.args["maxdepth"] ? parseInt(command.args["maxdepth"], 10) : Infinity;
4878
+ const maxDepthArg = command.args["maxdepth"];
4879
+ const parsedDepth = parseInt(String(maxDepthArg ?? ""), 10);
4880
+ const maxDepth = maxDepthArg != null && maxDepthArg !== true && !isNaN(parsedDepth) ? parsedDepth : Infinity;
4135
4881
  try {
4136
4882
  const node = fs.getNode(searchPath);
4137
4883
  if (!node) {
@@ -4140,24 +4886,25 @@ var files = (() => {
4140
4886
  );
4141
4887
  return;
4142
4888
  }
4143
- if (node.type !== "directory") {
4144
- context.writer.writeError(
4145
- `find: '${searchPath}': Not a directory`
4146
- );
4147
- return;
4148
- }
4149
4889
  const resolvedBase = fs.resolvePath(searchPath);
4150
- const nameRegex = namePattern ? this.globToRegex(namePattern) : null;
4890
+ const nameRegex = this.nameRegex(command);
4151
4891
  const results = [];
4152
- this.walk(
4153
- node,
4154
- resolvedBase,
4155
- 0,
4156
- maxDepth,
4157
- nameRegex,
4158
- typeFilter,
4159
- results
4160
- );
4892
+ this.fs = fs;
4893
+ const baseName = resolvedBase.split("/").filter(Boolean).pop() ?? "/";
4894
+ if (this.matches(baseName, node.type, nameRegex, typeFilter)) {
4895
+ results.push(resolvedBase);
4896
+ }
4897
+ if (node.type === "directory" && maxDepth > 0) {
4898
+ this.walk(
4899
+ node,
4900
+ resolvedBase,
4901
+ 1,
4902
+ maxDepth,
4903
+ nameRegex,
4904
+ typeFilter,
4905
+ results
4906
+ );
4907
+ }
4161
4908
  for (const r of results) {
4162
4909
  context.writer.writeln(r);
4163
4910
  }
@@ -4165,31 +4912,41 @@ var files = (() => {
4165
4912
  context.writer.writeError(`find: ${e.message}`);
4166
4913
  }
4167
4914
  }
4915
+ /** `--name` matches case-sensitively, `--iname` ignoring case. */
4916
+ nameRegex(command) {
4917
+ const name = command.args["name"];
4918
+ if (name != null && name !== true) {
4919
+ return this.globToRegex(String(name), false);
4920
+ }
4921
+ const iname = command.args["iname"];
4922
+ if (iname != null && iname !== true) {
4923
+ return this.globToRegex(String(iname), true);
4924
+ }
4925
+ return null;
4926
+ }
4927
+ matches(name, type, nameRegex, typeFilter) {
4928
+ if (nameRegex && !nameRegex.test(name)) return false;
4929
+ if (typeFilter === "f" && type !== "file") return false;
4930
+ if (typeFilter === "d" && type !== "directory") return false;
4931
+ return true;
4932
+ }
4168
4933
  parseSearchPath(command) {
4169
4934
  const raw = command.value || "";
4170
- const tokens = raw.split(/\s+/).filter(Boolean);
4935
+ const tokens = splitArgs(raw).filter(Boolean);
4171
4936
  return tokens.length > 0 ? tokens[0] : null;
4172
4937
  }
4173
- globToRegex(pattern) {
4938
+ globToRegex(pattern, ignoreCase) {
4174
4939
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
4175
4940
  const regexStr = escaped.replace(/\*/g, ".*").replace(/\?/g, ".");
4176
- return new RegExp(`^${regexStr}$`, "i");
4941
+ return new RegExp(`^${regexStr}$`, ignoreCase ? "i" : "");
4177
4942
  }
4943
+ /** Lists the children of `node`, which are at `depth` (1 = direct children). */
4178
4944
  walk(node, currentPath, depth, maxDepth, nameRegex, typeFilter, results) {
4179
- if (!node.children) return;
4180
- for (const child of node.children) {
4945
+ const children = this.fs ? readableChildren(this.fs, currentPath) : node.children;
4946
+ if (!children) return;
4947
+ for (const child of children) {
4181
4948
  const childPath = currentPath === "/" ? `/${child.name}` : `${currentPath}/${child.name}`;
4182
- let matches = true;
4183
- if (nameRegex && !nameRegex.test(child.name)) {
4184
- matches = false;
4185
- }
4186
- if (typeFilter === "f" && child.type !== "file") {
4187
- matches = false;
4188
- }
4189
- if (typeFilter === "d" && child.type !== "directory") {
4190
- matches = false;
4191
- }
4192
- if (matches) {
4949
+ if (this.matches(child.name, child.type, nameRegex, typeFilter)) {
4193
4950
  results.push(childPath);
4194
4951
  }
4195
4952
  if (child.type === "directory" && depth < maxDepth) {
@@ -4245,6 +5002,13 @@ var files = (() => {
4245
5002
  required: false,
4246
5003
  type: "boolean"
4247
5004
  },
5005
+ {
5006
+ name: "quiet",
5007
+ aliases: ["q"],
5008
+ description: "Print nothing; only the exit status tells whether a line matched",
5009
+ required: false,
5010
+ type: "boolean"
5011
+ },
4248
5012
  {
4249
5013
  name: "files-with-matches",
4250
5014
  aliases: ["l"],
@@ -4258,11 +5022,52 @@ var files = (() => {
4258
5022
  description: "Select non-matching lines",
4259
5023
  required: false,
4260
5024
  type: "boolean"
5025
+ },
5026
+ {
5027
+ name: "word-regexp",
5028
+ aliases: ["w"],
5029
+ description: "Match only whole words",
5030
+ required: false,
5031
+ type: "boolean"
5032
+ },
5033
+ {
5034
+ name: "only-matching",
5035
+ aliases: ["o"],
5036
+ description: "Print only the matched parts, one per line",
5037
+ required: false,
5038
+ type: "boolean"
5039
+ },
5040
+ {
5041
+ name: "extended-regexp",
5042
+ aliases: ["E"],
5043
+ description: "Extended regular expressions (patterns always are)",
5044
+ required: false,
5045
+ type: "boolean"
4261
5046
  }
4262
5047
  ];
4263
5048
  }
5049
+ writeDescription(context) {
5050
+ const t = context.translator;
5051
+ writeWrapped(
5052
+ context,
5053
+ t.t("cli.grep.help.print_lines_match_pattern", "Print the lines that match a pattern. The pattern is a regular expression, matches are highlighted, and the input can be files, directories (with -r) or piped text.")
5054
+ );
5055
+ context.writer.writeln();
5056
+ writeUsage(context, [
5057
+ ["grep <pattern> <file...>", t.t("cli.grep.help.search_one_more_files", "Search one or more files")],
5058
+ ["grep -r <pattern> <dir>", t.t("cli.grep.help.search_every_file_under", "Search every file under a directory")],
5059
+ ["<command> | grep <pattern>", t.t("cli.grep.help.filter_piped_input", "Filter piped input")]
5060
+ ]);
5061
+ context.writer.writeln();
5062
+ writeExamples(context, [
5063
+ ["grep -i error app.log", t.t("cli.grep.help.case_insensitive_search", "Case-insensitive search")],
5064
+ ["grep -r -n TODO src", t.t("cli.grep.help.find_todos_line_numbers", "Find TODOs with line numbers")],
5065
+ ["grep -v ^# config.txt", t.t("cli.grep.help.hide_comment_lines", "Hide comment lines")],
5066
+ ["cat app.log | grep -c ERROR", t.t("cli.grep.help.count_matching_lines", "Count matching lines")]
5067
+ ]);
5068
+ }
4264
5069
  async processCommand(command, context) {
4265
- const fs = context.services.get(
5070
+ const fs = context.services.getRequired(
4266
5071
  IFileSystemService_TOKEN
4267
5072
  );
4268
5073
  const ignoreCase = command.args["ignore-case"] || command.args["i"] || false;
@@ -4271,51 +5076,56 @@ var files = (() => {
4271
5076
  const countOnly = command.args["count"] || command.args["c"] || false;
4272
5077
  const filesOnly = command.args["files-with-matches"] || command.args["l"] || false;
4273
5078
  const invert = command.args["invert-match"] || command.args["v"] || false;
5079
+ const wordRegexp = command.args["word-regexp"] || command.args["w"] || false;
5080
+ const onlyMatching = command.args["only-matching"] || command.args["o"] || false;
5081
+ const quiet = command.args["quiet"] || command.args["q"] || false;
4274
5082
  const { pattern, paths } = this.parseArgs(command);
4275
5083
  if (!pattern) {
4276
5084
  context.writer.writeError(
4277
5085
  "grep: missing pattern. Usage: grep [options] <pattern> <file>"
4278
5086
  );
5087
+ context.process.exit(2, { silent: true });
5088
+ return;
5089
+ }
5090
+ let regex;
5091
+ try {
5092
+ const source = wordRegexp ? `(?<!\\w)(?:${pattern})(?!\\w)` : pattern;
5093
+ regex = new RegExp(source, ignoreCase ? "gi" : "g");
5094
+ } catch {
5095
+ context.writer.writeError(`grep: invalid pattern '${pattern}'`);
5096
+ context.process.exit(2, { silent: true });
4279
5097
  return;
4280
5098
  }
4281
5099
  if (paths.length === 0) {
4282
5100
  if (command.data != null) {
4283
- let regex2;
4284
- try {
4285
- regex2 = new RegExp(pattern, ignoreCase ? "gi" : "g");
4286
- } catch {
4287
- context.writer.writeError(`grep: invalid pattern '${pattern}'`);
4288
- return;
4289
- }
4290
5101
  const content = typeof command.data === "string" ? command.data : JSON.stringify(command.data);
4291
- this.grepContent(content, null, regex2, {
5102
+ const found2 = this.grepContent(content, null, regex, {
4292
5103
  ignoreCase,
4293
5104
  showLineNum,
4294
5105
  countOnly,
4295
5106
  filesOnly,
4296
- invert
5107
+ invert,
5108
+ onlyMatching,
5109
+ quiet
4297
5110
  }, context, false);
5111
+ this.exitStatus(context, found2, false);
4298
5112
  return;
4299
5113
  }
4300
5114
  context.writer.writeError(
4301
5115
  "grep: missing file operand. Usage: grep [options] <pattern> <file>"
4302
5116
  );
4303
- return;
4304
- }
4305
- let regex;
4306
- try {
4307
- regex = new RegExp(pattern, ignoreCase ? "gi" : "g");
4308
- } catch {
4309
- context.writer.writeError(`grep: invalid pattern '${pattern}'`);
5117
+ context.process.exit(2, { silent: true });
4310
5118
  return;
4311
5119
  }
4312
5120
  const filePaths = [];
5121
+ let failed = false;
4313
5122
  for (const p of paths) {
4314
5123
  try {
4315
5124
  if (!fs.exists(p)) {
4316
5125
  context.writer.writeError(
4317
5126
  `grep: ${p}: No such file or directory`
4318
5127
  );
5128
+ failed = true;
4319
5129
  continue;
4320
5130
  }
4321
5131
  if (fs.isDirectory(p)) {
@@ -4323,6 +5133,7 @@ var files = (() => {
4323
5133
  context.writer.writeError(
4324
5134
  `grep: ${p}: Is a directory`
4325
5135
  );
5136
+ failed = true;
4326
5137
  continue;
4327
5138
  }
4328
5139
  this.collectFiles(fs, fs.resolvePath(p), fs.getNode(p), filePaths);
@@ -4331,26 +5142,40 @@ var files = (() => {
4331
5142
  }
4332
5143
  } catch (e) {
4333
5144
  context.writer.writeError(`grep: ${e.message}`);
5145
+ failed = true;
4334
5146
  }
4335
5147
  }
4336
5148
  const multiFile = filePaths.length > 1;
5149
+ let found = 0;
4337
5150
  for (const filePath of filePaths) {
4338
5151
  try {
4339
5152
  const content = fs.readFile(filePath) ?? "";
4340
- this.grepContent(content, filePath, regex, {
5153
+ found += this.grepContent(content, filePath, regex, {
4341
5154
  ignoreCase,
4342
5155
  showLineNum,
4343
5156
  countOnly,
4344
5157
  filesOnly,
4345
- invert
5158
+ invert,
5159
+ onlyMatching,
5160
+ quiet
4346
5161
  }, context, multiFile);
4347
5162
  } catch (e) {
4348
5163
  context.writer.writeError(`grep: ${filePath}: ${e.message}`);
5164
+ failed = true;
4349
5165
  }
4350
5166
  }
5167
+ this.exitStatus(context, found, failed);
5168
+ }
5169
+ /**
5170
+ * As Unix grep: 0 when a line matched, 1 when none did, 2 after an
5171
+ * error (so `if grep -q ...` and `grep x f && ...` work).
5172
+ */
5173
+ exitStatus(context, found, failed) {
5174
+ if (found > 0) return;
5175
+ context.process.exit(failed ? 2 : 1, { silent: true });
4351
5176
  }
4352
5177
  grepContent(content, filePath, regex, options, context, multiFile) {
4353
- const lines = content.split("\n");
5178
+ const lines = splitLines(content);
4354
5179
  let matchCount = 0;
4355
5180
  const matchingLines = [];
4356
5181
  for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
@@ -4363,6 +5188,7 @@ var files = (() => {
4363
5188
  matchingLines.push({ num: lineIdx + 1, text: line });
4364
5189
  }
4365
5190
  }
5191
+ if (options.quiet) return matchCount;
4366
5192
  if (options.filesOnly) {
4367
5193
  if (matchCount > 0 && filePath) {
4368
5194
  context.writer.writeln(filePath);
@@ -4381,6 +5207,18 @@ var files = (() => {
4381
5207
  parts.push(context.writer.wrapInColor(String(m.num), CliForegroundColor.Green));
4382
5208
  parts.push(":");
4383
5209
  }
5210
+ if (options.onlyMatching && !options.invert) {
5211
+ regex.lastIndex = 0;
5212
+ const prefix = parts.join("");
5213
+ for (const match of m.text.match(regex) ?? []) {
5214
+ if (match !== "") {
5215
+ context.writer.writeln(
5216
+ prefix + context.writer.wrapInColor(match, CliForegroundColor.Red)
5217
+ );
5218
+ }
5219
+ }
5220
+ continue;
5221
+ }
4384
5222
  if (!options.invert) {
4385
5223
  regex.lastIndex = 0;
4386
5224
  const highlighted = m.text.replace(
@@ -4394,10 +5232,11 @@ var files = (() => {
4394
5232
  context.writer.writeln(parts.join(""));
4395
5233
  }
4396
5234
  }
5235
+ return matchCount;
4397
5236
  }
4398
5237
  parseArgs(command) {
4399
5238
  const raw = command.value || "";
4400
- const tokens = raw.split(/\s+/).filter(Boolean);
5239
+ const tokens = splitArgs(raw).filter(Boolean);
4401
5240
  if (tokens.length === 0) {
4402
5241
  return { pattern: null, paths: [] };
4403
5242
  }
@@ -4410,8 +5249,9 @@ var files = (() => {
4410
5249
  };
4411
5250
  }
4412
5251
  collectFiles(fs, basePath, node, result) {
4413
- if (!node.children) return;
4414
- for (const child of node.children) {
5252
+ const children = readableChildren(fs, basePath);
5253
+ if (!children) return;
5254
+ for (const child of children) {
4415
5255
  const childPath = basePath === "/" ? `/${child.name}` : `${basePath}/${child.name}`;
4416
5256
  if (child.type === "file") {
4417
5257
  result.push(childPath);
@@ -4443,17 +5283,14 @@ var files = (() => {
4443
5283
  ];
4444
5284
  }
4445
5285
  async processCommand(command, context) {
4446
- const fs = context.services.get(
5286
+ const fs = context.services.getRequired(
4447
5287
  IFileSystemService_TOKEN
4448
5288
  );
4449
- let filePath = (command.value || "").trim();
4450
- if (!filePath && command.rawCommand) {
4451
- const raw = command.rawCommand.trim();
4452
- const firstSpace = raw.indexOf(" ");
4453
- if (firstSpace > -1) {
4454
- filePath = raw.slice(firstSpace + 1).trim();
4455
- }
5289
+ let words = splitArgs(command.value || "");
5290
+ if (words.length === 0 && command.rawCommand) {
5291
+ words = splitArgs(command.rawCommand).slice(1).filter((w) => !w.startsWith("-"));
4456
5292
  }
5293
+ const filePath = words[0] ?? "";
4457
5294
  if (!filePath) {
4458
5295
  context.writer.writeError("sh: missing file operand");
4459
5296
  context.process.exit(1, { silent: true });
@@ -4486,10 +5323,10 @@ var files = (() => {
4486
5323
  await executeScript(content, context);
4487
5324
  }
4488
5325
  writeDescription(context) {
4489
- const { writer } = context;
4490
- writer.writeln(this.description);
5326
+ const { writer, translator: t } = context;
5327
+ writer.writeln(t.t("cli.sh.description", this.description));
4491
5328
  writer.writeln();
4492
- writer.writeln("Usage:");
5329
+ writer.writeln(t.t("cli.common.usage", "Usage:"));
4493
5330
  writer.writeln(
4494
5331
  ` ${writer.wrapInColor("sh <script.sh>", CliForegroundColor.Cyan)}`
4495
5332
  );
@@ -4500,78 +5337,51 @@ var files = (() => {
4500
5337
  ` ${writer.wrapInColor(". <script.sh>", CliForegroundColor.Cyan)}`
4501
5338
  );
4502
5339
  writer.writeln(
4503
- ` ${writer.wrapInColor("./script.sh", CliForegroundColor.Cyan)} (requires execute permission)`
5340
+ ` ${writer.wrapInColor("./script.sh", CliForegroundColor.Cyan)} ${t.t("cli.sh.help.requires_execute", "(requires execute permission)")}`
4504
5341
  );
4505
5342
  writer.writeln();
4506
- writer.writeln("Script syntax:");
4507
- writer.writeln(" # Lines starting with # are comments");
4508
- writer.writeln(" VAR=value # Variable assignment");
4509
- writer.writeln(" echo $VAR # Variable substitution");
4510
- writer.writeln(" set -e # Stop on error (default)");
4511
- writer.writeln(" set +e # Continue on errors");
5343
+ writer.writeln(t.t("cli.sh.help.script_syntax", "Script syntax:"));
5344
+ writer.writeln(` # ${t.t("cli.sh.help.comments", "Lines starting with # are comments")}`);
5345
+ writer.writeln(` cmd1; cmd2 # ${t.t("cli.sh.help.several_commands", "Several commands on one line")}`);
5346
+ writer.writeln(` VAR=value # ${t.t("cli.sh.help.assignment", "Variable assignment")}`);
5347
+ writer.writeln(` echo $VAR # ${t.t("cli.sh.help.substitution", "Variable substitution")}`);
5348
+ writer.writeln(` set -e # ${t.t("cli.sh.help.stop_on_error", "Stop on error (default)")}`);
5349
+ writer.writeln(` set +e # ${t.t("cli.sh.help.continue_on_errors", "Continue on errors")}`);
5350
+ writer.writeln(` exit [n] # ${t.t("cli.sh.help.exit", "Stop the script with status n")}`);
5351
+ writer.writeln();
5352
+ writer.writeln(t.t("cli.sh.help.control_flow", "Control flow:"));
5353
+ writer.writeln(" if cmd; then ...; elif cmd; then ...; else ...; fi");
5354
+ writer.writeln(" for NAME in a b *.txt; do ...; done");
5355
+ writer.writeln(` while cmd; do ...; done ${t.t("cli.sh.help.until", "(until cmd: the opposite)")}`);
5356
+ writer.writeln(` break / continue [n] # ${t.t("cli.sh.help.break_continue", "Leave / skip in a loop")}`);
5357
+ writer.writeln(` ! cmd # ${t.t("cli.sh.help.negate", "Negate the exit status")}`);
5358
+ writer.writeln(
5359
+ ` ${t.t("cli.sh.help.conditions", "Conditions are commands:")} ${writer.wrapInColor("[ -f file ]", CliForegroundColor.Cyan)}, ${writer.wrapInColor('test "$A" = b', CliForegroundColor.Cyan)}, ${writer.wrapInColor("true", CliForegroundColor.Cyan)}, ${writer.wrapInColor("grep x file", CliForegroundColor.Cyan)}`
5360
+ );
5361
+ writer.writeln(` ${t.t("cli.sh.help.conditions_status", "(status 0 = true; see help test). Loops stop after 10000 iterations")}`);
5362
+ writer.writeln(` ${t.t("cli.sh.help.conditions_loops", "of while/until; Ctrl+C stops the script.")}`);
4512
5363
  writer.writeln();
4513
- writer.writeln("Example script:");
5364
+ writer.writeln(t.t("cli.sh.help.example_script", "Example script:"));
4514
5365
  writer.writeln(
4515
5366
  writer.wrapInColor(
4516
- " #!/bin/sh\n # Setup script\n NAME=world\n echo Hello $NAME\n mkdir -p /tmp/test\n echo done > /tmp/test/status.txt",
5367
+ " #!/bin/sh\n # Setup script\n NAME=world\n echo Hello $NAME\n mkdir -p /tmp/test\n for f in a b c; do\n echo $f > /tmp/test/$f.txt\n done\n if [ -f /tmp/test/a.txt ]; then\n echo done > /tmp/test/status.txt\n fi",
4517
5368
  CliForegroundColor.Green
4518
5369
  )
4519
5370
  );
4520
5371
  }
4521
5372
  };
5373
+ var SCRIPT_RUNNER_TOKEN = "cli-script-runner";
4522
5374
  async function executeScript(content, context) {
4523
- const lines = content.split("\n");
4524
- const variables = {};
4525
- let stopOnError = true;
4526
- for (const rawLine of lines) {
4527
- const line = rawLine.trim();
4528
- if (!line || line.startsWith("#")) {
4529
- continue;
4530
- }
4531
- if (line === "set -e") {
4532
- stopOnError = true;
4533
- continue;
4534
- }
4535
- if (line === "set +e") {
4536
- stopOnError = false;
4537
- continue;
4538
- }
4539
- const assignMatch = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
4540
- if (assignMatch) {
4541
- const varName = assignMatch[1];
4542
- let varValue = assignMatch[2];
4543
- if (varValue.startsWith('"') && varValue.endsWith('"') || varValue.startsWith("'") && varValue.endsWith("'")) {
4544
- varValue = varValue.slice(1, -1);
4545
- }
4546
- varValue = substituteVariables(varValue, variables);
4547
- variables[varName] = varValue;
4548
- continue;
4549
- }
4550
- const expanded = substituteVariables(line, variables);
4551
- await context.executor.executeCommand(expanded, context);
4552
- const exitCode = context.process.exitCode;
4553
- if (stopOnError && exitCode !== void 0 && exitCode !== 0) {
4554
- context.writer.writeError(
4555
- `sh: script aborted at line: ${line}`
4556
- );
4557
- return;
4558
- }
5375
+ const runner = context.services.get(SCRIPT_RUNNER_TOKEN);
5376
+ if (!runner) {
5377
+ context.writer.writeError("sh: script runner not available (update @qodalis/cli)");
5378
+ context.process.exit(1, { silent: true });
5379
+ return;
4559
5380
  }
4560
- }
4561
- function substituteVariables(input, variables) {
4562
- let result = input.replace(
4563
- /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,
4564
- (_, name) => variables[name] ?? ""
4565
- );
4566
- result = result.replace(
4567
- /\$([A-Za-z_][A-Za-z0-9_]*)/g,
4568
- (_, name) => variables[name] ?? ""
4569
- );
4570
- return result;
5381
+ await runner.run(content, context, { name: "sh" });
4571
5382
  }
4572
5383
 
4573
5384
  // src/cli-entrypoint.ts
4574
- var fsService = new IndexedDbFileSystemService();
4575
5385
  var module = {
4576
5386
  apiVersion: API_VERSION2,
4577
5387
  name: "@qodalis/cli-files",
@@ -4598,16 +5408,17 @@ var files = (() => {
4598
5408
  services: [
4599
5409
  {
4600
5410
  provide: IFileSystemService_TOKEN,
4601
- useValue: fsService
5411
+ useClass: IndexedDbFileSystemService
4602
5412
  },
4603
5413
  {
4604
5414
  provide: ICliCompletionProvider_TOKEN,
4605
- useValue: new FilePathCompletionProvider(fsService),
5415
+ useClass: FilePathCompletionProvider,
5416
+ deps: [IFileSystemService_TOKEN],
4606
5417
  multi: true
4607
5418
  }
4608
5419
  ],
4609
5420
  async onInit(context) {
4610
- const fs = context.services.get(
5421
+ const fs = context.services.getRequired(
4611
5422
  IFileSystemService_TOKEN
4612
5423
  );
4613
5424
  await fs.initialize();