@roulabs/mx 1.2.2 → 1.7.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.
Files changed (2) hide show
  1. package/bin/mx.js +256 -102
  2. package/package.json +1 -1
package/bin/mx.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/main.ts
4
4
  import { readFileSync as readFileSync2 } from "fs";
5
- import * as path7 from "path";
5
+ import * as path8 from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
 
8
8
  // ../../packages/core/src/errors.ts
@@ -133,6 +133,11 @@ function writeWork(root, work) {
133
133
  function findWorktree(work, repo) {
134
134
  return (work.worktrees ?? []).find((w) => w.repo === repo) ?? null;
135
135
  }
136
+ function countSessions(root, workName) {
137
+ const dir = path3.join(workDir(root, workName), "sessions");
138
+ if (!exists(dir)) return 0;
139
+ return fs4.readdirSync(dir).filter((n) => n.endsWith(".md")).length;
140
+ }
136
141
  function inferContext(root) {
137
142
  const cwd = realpath(process.cwd());
138
143
  const segmentsUnder = (base) => {
@@ -328,19 +333,13 @@ function workNew(root, name, description = "") {
328
333
  return { ...work, path: dir };
329
334
  }
330
335
  function listWorksInfo(root, opts = {}) {
331
- return listWorkNames(root).map((name) => {
332
- const w = readWork(root, name);
333
- return {
334
- name,
335
- description: w.description ?? "",
336
- worktrees: (w.worktrees ?? []).length,
337
- isArchived: w.isArchived === true,
338
- archived_at: w.archived_at ?? null
339
- };
340
- }).filter((s) => {
341
- if (opts.onlyArchived) return s.isArchived;
336
+ return listWorkNames(root).map((name) => ({
337
+ ...readWork(root, name),
338
+ sessions: countSessions(root, name)
339
+ })).filter((w) => {
340
+ if (opts.onlyArchived) return w.isArchived === true;
342
341
  if (opts.includeArchived) return true;
343
- return !s.isArchived;
342
+ return w.isArchived !== true;
344
343
  });
345
344
  }
346
345
  function workInfo(root, name) {
@@ -567,10 +566,24 @@ function portList(root, name) {
567
566
  }
568
567
 
569
568
  // ../../packages/core/src/status.ts
569
+ import * as path5 from "path";
570
+ function countContextEntries(root) {
571
+ const indexPath = path5.join(root, "context", "INDEX.json");
572
+ if (!exists(indexPath)) return 0;
573
+ try {
574
+ const data = readJson(indexPath);
575
+ return Array.isArray(data) ? data.length : 0;
576
+ } catch {
577
+ return 0;
578
+ }
579
+ }
570
580
  function statusRuntime(root) {
571
- const repos = listReposInfo(root);
572
- const works = listWorkNames(root).map((name) => readWork(root, name));
573
- return { runtime: root, repos, works };
581
+ return {
582
+ runtime: root,
583
+ context: { entries: countContextEntries(root) },
584
+ repos: listReposInfo(root),
585
+ works: listWorksInfo(root, { includeArchived: true })
586
+ };
574
587
  }
575
588
 
576
589
  // src/args.ts
@@ -589,7 +602,6 @@ function parseArgs(argv) {
589
602
  help: false,
590
603
  version: false,
591
604
  force: false,
592
- all: false,
593
605
  archived: false
594
606
  };
595
607
  for (let i = 0; i < argv.length; i++) {
@@ -602,8 +614,6 @@ function parseArgs(argv) {
602
614
  flags.version = true;
603
615
  } else if (a === "--force") {
604
616
  flags.force = true;
605
- } else if (a === "--all") {
606
- flags.all = true;
607
617
  } else if (a === "--archived") {
608
618
  flags.archived = true;
609
619
  } else if (a.startsWith("--") && a.includes("=")) {
@@ -621,6 +631,22 @@ function parseArgs(argv) {
621
631
  }
622
632
 
623
633
  // src/output.ts
634
+ var USE_STYLE = process.stdout.isTTY && !process.env.NO_COLOR;
635
+ function wrap(s, code) {
636
+ return USE_STYLE ? `\x1B[${code}m${s}\x1B[0m` : s;
637
+ }
638
+ function dim(s) {
639
+ return wrap(s, 2);
640
+ }
641
+ function bold(s) {
642
+ return wrap(s, 1);
643
+ }
644
+ function check() {
645
+ return "\u2713";
646
+ }
647
+ function warn() {
648
+ return "\u26A0";
649
+ }
624
650
  var porcelain = false;
625
651
  function setPorcelain(value) {
626
652
  porcelain = value;
@@ -640,7 +666,9 @@ function fail(err) {
640
666
  if (porcelain) {
641
667
  process.stdout.write(JSON.stringify({ error: message, code }, null, 2) + "\n");
642
668
  } else {
643
- process.stderr.write(`mx: ${message}
669
+ const useStyle = process.stderr.isTTY && !process.env.NO_COLOR;
670
+ const prefix = useStyle ? `\x1B[1mmx:\x1B[0m` : "mx:";
671
+ process.stderr.write(`${prefix} ${message}
644
672
  `);
645
673
  }
646
674
  process.exit(1);
@@ -651,8 +679,8 @@ var HELP = `mx \u2014 control panel for the mx runtime
651
679
 
652
680
  Global:
653
681
  mx init [path] scaffold/adopt a runtime (default ~/mx)
654
- mx status [--porcelain] show runtime, repos, works, ports
655
- mx update re-stamp runtime CLAUDE.md from templates
682
+ mx status [--porcelain] show runtime, repos, works, ports (aliases: mx s, mx st)
683
+ mx update re-sync runtime from current templates + backfill structural scaffolding
656
684
  mx help | version
657
685
 
658
686
  Repos (pristine clones):
@@ -664,7 +692,7 @@ Repos (pristine clones):
664
692
 
665
693
  Works (features):
666
694
  mx work new <name> [--description <t>] creates folder + empty work.json + sessions/
667
- mx work ls [--all|--archived] [--porcelain] default: active only
695
+ mx work ls [--archived] [--porcelain] default: all works (archived marked); --archived filters to archived only
668
696
  mx work -n <name> info [--porcelain]
669
697
  mx work -n <name> path print the work folder path (cd "$(mx work -n <name> path)")
670
698
  mx work -n <name> describe <text>
@@ -686,33 +714,33 @@ Runtime discovery: --runtime <path> -> $MX_RUNTIME -> default ~/mx.
686
714
  `;
687
715
 
688
716
  // src/commands/global.ts
689
- import * as path6 from "path";
717
+ import * as path7 from "path";
690
718
 
691
719
  // src/paths.ts
692
- import * as path5 from "path";
720
+ import * as path6 from "path";
693
721
  import { fileURLToPath } from "url";
694
722
  function templatesDir() {
695
723
  if (process.env.MX_TEMPLATES_DIR) return process.env.MX_TEMPLATES_DIR;
696
- const here = path5.dirname(fileURLToPath(import.meta.url));
697
- return path5.join(here, "..", "templates");
724
+ const here = path6.dirname(fileURLToPath(import.meta.url));
725
+ return path6.join(here, "..", "templates");
698
726
  }
699
727
 
700
728
  // src/commands/global.ts
701
729
  function runtimeEnvHint(runtime) {
702
- const envRuntime = process.env.MX_RUNTIME ? path6.resolve(process.env.MX_RUNTIME) : null;
730
+ const envRuntime = process.env.MX_RUNTIME ? path7.resolve(process.env.MX_RUNTIME) : null;
703
731
  if (envRuntime === runtime) {
704
- return ["", `$MX_RUNTIME already points here \u2014 you're set.`];
732
+ return ["", `${dim("$MX_RUNTIME already points here \u2014 you're set.")}`];
705
733
  }
706
734
  if (runtime === defaultRuntime() && !envRuntime) {
707
- return ["", `This is the default mx runtime (~/mx) \u2014 no MX_RUNTIME setup needed.`];
735
+ return ["", `${dim("This is the default mx runtime (~/mx) \u2014 no MX_RUNTIME setup needed.")}`];
708
736
  }
709
737
  return [
710
738
  "",
711
- `Point mx at this runtime by adding to your shell config (~/.zshrc, ~/.bashrc):`,
739
+ `Point mx at this runtime by adding to your shell config ${dim("(~/.zshrc, ~/.bashrc)")}:`,
712
740
  "",
713
- ` export MX_RUNTIME="${runtime}"`,
741
+ ` ${bold(`export MX_RUNTIME="${runtime}"`)}`,
714
742
  "",
715
- `Without it, future \`mx\` commands fall back to the default ~/mx.`
743
+ dim("Without it, future `mx` commands fall back to the default ~/mx.")
716
744
  ];
717
745
  }
718
746
  function runGlobal(positionals, flags) {
@@ -721,8 +749,8 @@ function runGlobal(positionals, flags) {
721
749
  const target = positionals[1] || discoverRuntime({ runtime: flags.runtime });
722
750
  const res = initRuntime(target, templatesDir());
723
751
  emit(() => {
724
- console.log(`Runtime ready at ${res.runtime}`);
725
- for (const c of res.created) console.log(` + ${c}`);
752
+ console.log(`${check()} Runtime ready at ${bold(res.runtime)}`);
753
+ for (const c of res.created) console.log(` ${dim(`+ ${c}`)}`);
726
754
  for (const line of runtimeEnvHint(res.runtime)) console.log(line);
727
755
  }, res);
728
756
  return;
@@ -730,31 +758,15 @@ function runGlobal(positionals, flags) {
730
758
  case "status": {
731
759
  const root = requireRuntime({ runtime: flags.runtime });
732
760
  const data = statusRuntime(root);
733
- emit(() => {
734
- console.log(`runtime: ${data.runtime}
735
- `);
736
- console.log(`repos (${data.repos.length}):`);
737
- for (const r of data.repos) {
738
- console.log(` ${r.name} [${r.branch}] ${r.remote ?? "(no remote)"}`);
739
- }
740
- console.log(`
741
- works (${data.works.length}):`);
742
- for (const w of data.works) {
743
- console.log(` ${w.name}${w.description ? ` \u2014 ${w.description}` : ""}`);
744
- for (const wt of w.worktrees) {
745
- const ports = Object.entries(wt.ports).map(([s, p]) => `${s}:${p}`).join(", ");
746
- console.log(` ${wt.repo} [${wt.branch}]${ports ? ` (${ports})` : ""}`);
747
- }
748
- }
749
- }, data);
761
+ emit(() => renderStatus(data), data);
750
762
  return;
751
763
  }
752
764
  case "update": {
753
765
  const root = requireRuntime({ runtime: flags.runtime });
754
766
  const res = updateRuntime(root, templatesDir());
755
767
  emit(() => {
756
- console.log(`Updated runtime at ${res.runtime}`);
757
- for (const p of res.updated) console.log(` + ${p}`);
768
+ console.log(`${check()} Updated runtime at ${bold(res.runtime)}`);
769
+ for (const p of res.updated) console.log(` ${dim(`+ ${p}`)}`);
758
770
  }, res);
759
771
  return;
760
772
  }
@@ -762,6 +774,61 @@ works (${data.works.length}):`);
762
774
  throw new MxError(`unknown command: ${positionals[0]}`, "BAD_ARGS");
763
775
  }
764
776
  }
777
+ function renderStatus(data) {
778
+ console.log();
779
+ console.log(` ${bold("mx")} ${dim("\xB7")} ${data.runtime}`);
780
+ console.log();
781
+ console.log(` ${bold("context")} ${dim(`(${data.context.entries})`)}`);
782
+ console.log();
783
+ console.log(` ${bold("repos")}`);
784
+ if (data.repos.length === 0) {
785
+ console.log(` ${dim("none yet \u2014 `mx repo add <git-url>`")}`);
786
+ } else {
787
+ const nameW = Math.max(...data.repos.map((r) => r.name.length));
788
+ const branchW = Math.max(...data.repos.map((r) => r.branch.length));
789
+ for (const r of data.repos) {
790
+ const name = r.name.padEnd(nameW);
791
+ const branch = dim(r.branch.padEnd(branchW));
792
+ const remote = dim(r.remote ?? "(no remote)");
793
+ console.log(` \u2022 ${name} ${branch} ${remote}`);
794
+ }
795
+ }
796
+ console.log();
797
+ const active = data.works.filter((w) => w.isArchived !== true);
798
+ const archived = data.works.filter((w) => w.isArchived === true);
799
+ const worksCount = archived.length > 0 ? dim(`(${active.length} active, ${archived.length} archived)`) : dim(`(${data.works.length})`);
800
+ console.log(` ${bold("works")} ${worksCount}`);
801
+ if (data.works.length === 0) {
802
+ console.log(` ${dim("none yet \u2014 `mx work new <name>`")}`);
803
+ console.log();
804
+ return;
805
+ }
806
+ const ordered = [...active, ...archived];
807
+ const wtRepoW = Math.max(
808
+ 0,
809
+ ...ordered.flatMap((w) => (w.worktrees ?? []).map((wt) => wt.repo.length))
810
+ );
811
+ for (let i = 0; i < ordered.length; i++) {
812
+ if (i > 0) console.log();
813
+ const w = ordered[i];
814
+ const wts = w.worktrees ?? [];
815
+ const chip = w.isArchived === true ? ` ${dim(`[archived ${(w.archived_at ?? "").slice(0, 10)}]`)}` : "";
816
+ const styledName = w.isArchived === true ? dim(w.name) : bold(w.name);
817
+ console.log(` \u2022 ${styledName}${chip}`);
818
+ if (wts.length === 0) {
819
+ console.log(` ${dim("(no worktrees)")}`);
820
+ continue;
821
+ }
822
+ for (const t of wts) {
823
+ const repo = dim(t.repo.padEnd(wtRepoW));
824
+ const branch = dim(`[${t.branch}]`);
825
+ const ports = Object.entries(t.ports ?? {}).map(([s, p]) => dim(`${s}:${p}`)).join(" ");
826
+ const portsCol = ports ? ` ${ports}` : "";
827
+ console.log(` ${repo} ${branch}${portsCol}`);
828
+ }
829
+ }
830
+ console.log();
831
+ }
765
832
 
766
833
  // src/commands/repo.ts
767
834
  function need(v, msg) {
@@ -776,13 +843,24 @@ function dispatchRepo(positionals, flags) {
776
843
  case "add": {
777
844
  const url = need(positionals[2], "usage: mx repo add <git-url> [--name <n>]");
778
845
  const res = repoAdd(root, url, flags.name);
779
- emit(() => console.log(`cloned ${res.name} -> ${res.path}`), res);
846
+ emit(() => console.log(`${check()} cloned ${bold(res.name)} ${dim(`\u2192 ${res.path}`)}`), res);
780
847
  return;
781
848
  }
782
849
  case "ls": {
783
850
  const repos = listReposInfo(root);
784
851
  emit(() => {
785
- for (const r of repos) console.log(`${r.name} [${r.branch}] ${r.remote ?? "(no remote)"}`);
852
+ if (repos.length === 0) {
853
+ console.log(dim("no repos yet \u2014 `mx repo add <git-url>`"));
854
+ return;
855
+ }
856
+ const nameW = Math.max(...repos.map((r) => r.name.length));
857
+ const branchW = Math.max(...repos.map((r) => r.branch.length));
858
+ for (const r of repos) {
859
+ const name = r.name.padEnd(nameW);
860
+ const branch = dim(r.branch.padEnd(branchW));
861
+ const remote = dim(r.remote ?? "(no remote)");
862
+ console.log(`\u2022 ${name} ${branch} ${remote}`);
863
+ }
786
864
  }, repos);
787
865
  return;
788
866
  }
@@ -794,7 +872,7 @@ function dispatchRepo(positionals, flags) {
794
872
  const res = repoFetch(root, name);
795
873
  emit(
796
874
  () => console.log(
797
- `fetched ${res.name} \u2014 ${res.remoteBranches.length} branch(es) on origin, now on ${res.branch}`
875
+ `${check()} fetched ${bold(res.name)} ${dim(`\u2014 ${res.remoteBranches.length} branch(es) on origin, now on ${res.branch}`)}`
798
876
  ),
799
877
  res
800
878
  );
@@ -807,15 +885,12 @@ function dispatchRepo(positionals, flags) {
807
885
  );
808
886
  const res = repoInfo(root, name);
809
887
  emit(() => {
810
- console.log(
811
- `${res.name}
812
- path: ${res.path}
813
- branch: ${res.branch}
814
- remote: ${res.remote ?? "(none)"}`
815
- );
816
- console.log(
817
- ` used by works: ${res.worktreesInWorks.length ? res.worktreesInWorks.join(", ") : "(none)"}`
818
- );
888
+ console.log(bold(res.name));
889
+ console.log(` ${dim("path ")} ${dim(res.path)}`);
890
+ console.log(` ${dim("branch")} ${dim(res.branch)}`);
891
+ console.log(` ${dim("remote")} ${dim(res.remote ?? "(none)")}`);
892
+ const usedBy = res.worktreesInWorks.length ? res.worktreesInWorks.join(", ") : "(none)";
893
+ console.log(` ${dim("used ")} ${dim(usedBy)}`);
819
894
  }, res);
820
895
  return;
821
896
  }
@@ -825,7 +900,7 @@ function dispatchRepo(positionals, flags) {
825
900
  "which repo? pass -n <name> or run inside a repo (mx repo -n <name> rm)"
826
901
  );
827
902
  const res = repoRemove(root, name);
828
- emit(() => console.log(`removed repo ${res.name}`), res);
903
+ emit(() => console.log(`${check()} removed repo ${bold(res.name)}`), res);
829
904
  return;
830
905
  }
831
906
  default:
@@ -845,23 +920,48 @@ function dispatchWork(positionals, flags) {
845
920
  const name2 = need2(positionals[2], "usage: mx work new <name> [--description <text>]");
846
921
  const res = workNew(root2, name2, flags.description ?? "");
847
922
  emit(() => {
848
- console.log(`created work ${res.name}`);
849
- console.log(` ${res.path}`);
923
+ console.log(`${check()} created work ${bold(res.name)}`);
924
+ console.log(` ${dim(res.path)}`);
850
925
  }, res);
851
926
  return;
852
927
  }
853
928
  if (action === "ls") {
854
929
  const root2 = requireRuntime({ runtime: flags.runtime });
855
930
  const works = listWorksInfo(root2, {
856
- includeArchived: flags.all,
931
+ includeArchived: true,
857
932
  onlyArchived: flags.archived
858
933
  });
859
934
  emit(() => {
860
- for (const w of works) {
861
- const chip = w.isArchived ? `[archived ${(w.archived_at ?? "").slice(0, 10)}] ` : "";
862
- const desc = w.description ? ` \u2014 ${w.description}` : "";
863
- const wts = `(${w.worktrees} worktree${w.worktrees === 1 ? "" : "s"})`;
864
- console.log(`${chip}${w.name} ${wts}${desc}`);
935
+ const ordered = [
936
+ ...works.filter((w) => w.isArchived !== true),
937
+ ...works.filter((w) => w.isArchived === true)
938
+ ];
939
+ if (ordered.length === 0) {
940
+ console.log(dim("no works yet \u2014 `mx work new <name>`"));
941
+ return;
942
+ }
943
+ for (let i = 0; i < ordered.length; i++) {
944
+ if (i > 0) console.log();
945
+ const w = ordered[i];
946
+ const wts = w.worktrees ?? [];
947
+ const chip = w.isArchived === true ? ` ${dim(`[archived ${(w.archived_at ?? "").slice(0, 10)}]`)}` : "";
948
+ const styledName = w.isArchived === true ? dim(w.name) : bold(w.name);
949
+ console.log(`\u2022 ${styledName}${chip}`);
950
+ if (w.description) {
951
+ console.log(` ${dim(`\u2014 ${w.description}`)}`);
952
+ }
953
+ if (wts.length === 0) {
954
+ console.log(` ${dim("(no worktrees)")}`);
955
+ } else {
956
+ const repoW = Math.max(...wts.map((t) => t.repo.length));
957
+ for (const t of wts) {
958
+ const repo = dim(t.repo.padEnd(repoW));
959
+ const branch = dim(`[${t.branch}]`);
960
+ const ports = Object.entries(t.ports ?? {}).map(([s, p]) => `${dim(`${s}:${p}`)}`).join(" ");
961
+ const portsCol = ports ? ` ${ports}` : "";
962
+ console.log(` ${repo} ${branch}${portsCol}`);
963
+ }
964
+ }
865
965
  }
866
966
  }, works);
867
967
  return;
@@ -874,7 +974,19 @@ function dispatchWork(positionals, flags) {
874
974
  switch (action) {
875
975
  case "info": {
876
976
  const work = workInfo(root, name);
877
- emit(() => console.log(JSON.stringify(work, null, 2)), work);
977
+ emit(() => {
978
+ const archivedChip = work.isArchived === true ? ` ${dim(`[archived ${(work.archived_at ?? "").slice(0, 10)}]`)}` : "";
979
+ const styledName = work.isArchived === true ? dim(work.name) : bold(work.name);
980
+ console.log(`${styledName}${archivedChip}`);
981
+ if (work.description) console.log(` ${dim("description")} ${dim(work.description)}`);
982
+ const wts = work.worktrees ?? [];
983
+ console.log(` ${dim("worktrees ")} ${dim(`${wts.length}`)}`);
984
+ for (const wt of wts) {
985
+ const ports = Object.entries(wt.ports ?? {}).map(([s, p]) => `${dim(`${s}:`)}${dim(String(p))}`).join(" ");
986
+ const portsCol = ports ? ` ${ports}` : "";
987
+ console.log(` ${dim(wt.repo)} ${dim(`[${wt.branch}]`)}${portsCol}`);
988
+ }
989
+ }, work);
878
990
  return;
879
991
  }
880
992
  case "path": {
@@ -885,7 +997,7 @@ function dispatchWork(positionals, flags) {
885
997
  case "describe": {
886
998
  const text = need2(positionals[2], "usage: mx work -n <name> describe <text>");
887
999
  const work = workDescribe(root, name, text);
888
- emit(() => console.log(`updated description of ${name}`), work);
1000
+ emit(() => console.log(`${check()} updated description of ${bold(name)}`), work);
889
1001
  return;
890
1002
  }
891
1003
  case "worktree":
@@ -895,33 +1007,32 @@ function dispatchWork(positionals, flags) {
895
1007
  case "destroy": {
896
1008
  if (flags.force && !flags.porcelain) {
897
1009
  process.stderr.write(
898
- `\u26A0 permanently removing work "${name}" \u2014 folder and any session summaries will be deleted (branches kept). This cannot be undone.
1010
+ `${warn()} ${dim(`permanently removing work "${name}" \u2014 folder and any session summaries will be deleted (branches kept). This cannot be undone.`)}
899
1011
  `
900
1012
  );
901
1013
  }
902
1014
  const res = workDestroy(root, name, { force: flags.force });
903
- emit(
904
- () => console.log(
905
- `destroyed work ${name} (worktrees removed: ${res.removedWorktrees.join(", ") || "none"}; branches kept)`
906
- ),
907
- res
908
- );
1015
+ emit(() => {
1016
+ const removed = res.removedWorktrees.join(", ") || "none";
1017
+ console.log(`${check()} destroyed work ${bold(name)}`);
1018
+ console.log(` ${dim(`worktrees removed: ${removed}; branches kept`)}`);
1019
+ }, res);
909
1020
  return;
910
1021
  }
911
1022
  case "archive": {
912
1023
  if (!flags.porcelain) {
913
1024
  process.stderr.write(
914
- `Reminder: write any pending session summary into works/${name}/sessions/ before archiving.
1025
+ `${warn()} ${dim(`Reminder: write any pending session summary into works/${name}/sessions/ before archiving.`)}
915
1026
  `
916
1027
  );
917
1028
  }
918
1029
  const res = archiveWork(root, name);
919
- emit(
920
- () => console.log(
921
- `archived work ${name} at ${res.archived_at} (worktrees removed: ${res.removedWorktrees.join(", ") || "none"}; branches kept)`
922
- ),
923
- res
924
- );
1030
+ emit(() => {
1031
+ const removed = res.removedWorktrees.join(", ") || "none";
1032
+ console.log(`${check()} archived work ${bold(name)}`);
1033
+ console.log(` ${dim(`at ${res.archived_at}`)}`);
1034
+ console.log(` ${dim(`worktrees removed: ${removed}; branches kept`)}`);
1035
+ }, res);
925
1036
  return;
926
1037
  }
927
1038
  case "unarchive": {
@@ -938,8 +1049,10 @@ function dispatchWork(positionals, flags) {
938
1049
  }
939
1050
  const res = unarchiveWork(root, name, overrides);
940
1051
  emit(() => {
941
- console.log(`unarchived work ${name}`);
942
- for (const r of res.restored) console.log(` ${r.repo} [${r.branch}] -> ${r.path}`);
1052
+ console.log(`${check()} unarchived work ${bold(name)}`);
1053
+ for (const r of res.restored) {
1054
+ console.log(` ${r.repo} ${dim(`[${r.branch}]`)} ${dim(`\u2192 ${r.path}`)}`);
1055
+ }
943
1056
  }, res);
944
1057
  return;
945
1058
  }
@@ -956,15 +1069,28 @@ function workWorktree(root, name, positionals, flags) {
956
1069
  "usage: mx work -n <name> worktree add <repo> [--branch <b>] [--base <ref>]"
957
1070
  );
958
1071
  const res = worktreeAdd(root, name, repo, { branch: flags.branch, base: flags.base });
959
- emit(() => console.log(`added worktree ${res.repo} [${res.branch}] -> ${res.path}`), res);
1072
+ emit(
1073
+ () => console.log(
1074
+ `${check()} added worktree ${bold(res.repo)} ${dim(`[${res.branch}]`)} ${dim(`\u2192 ${res.path}`)}`
1075
+ ),
1076
+ res
1077
+ );
960
1078
  return;
961
1079
  }
962
1080
  case "ls": {
963
1081
  const list = worktreeList(root, name);
964
1082
  emit(() => {
1083
+ if (list.length === 0) {
1084
+ console.log(dim("no worktrees yet \u2014 `mx work -n <name> worktree add <repo>`"));
1085
+ return;
1086
+ }
1087
+ const repoW = Math.max(...list.map((wt) => wt.repo.length));
965
1088
  for (const wt of list) {
966
- const ports = Object.entries(wt.ports ?? {}).map(([s, p]) => `${s}:${p}`).join(", ");
967
- console.log(`${wt.repo} [${wt.branch}]${ports ? ` (${ports})` : ""}`);
1089
+ const repo = dim(wt.repo.padEnd(repoW));
1090
+ const branch = dim(`[${wt.branch}]`);
1091
+ const ports = Object.entries(wt.ports ?? {}).map(([s, p]) => `${dim(`${s}:`)}${dim(String(p))}`).join(" ");
1092
+ const portsCol = ports ? ` ${ports}` : "";
1093
+ console.log(`${repo} ${branch}${portsCol}`);
968
1094
  }
969
1095
  }, list);
970
1096
  return;
@@ -972,7 +1098,12 @@ function workWorktree(root, name, positionals, flags) {
972
1098
  case "rm": {
973
1099
  const repo = need2(positionals[3], "usage: mx work -n <name> worktree rm <repo>");
974
1100
  const res = worktreeRemove(root, name, repo);
975
- emit(() => console.log(`removed worktree ${res.repo} from ${name} (branch ${res.branch} kept)`), res);
1101
+ emit(
1102
+ () => console.log(
1103
+ `${check()} removed worktree ${bold(res.repo)} ${dim(`from ${name} (branch ${res.branch} kept)`)}`
1104
+ ),
1105
+ res
1106
+ );
976
1107
  return;
977
1108
  }
978
1109
  default:
@@ -993,7 +1124,12 @@ function workPort(root, name, positionals) {
993
1124
  if (!Number.isInteger(port)) throw new MxError(`invalid port: ${portArg}`, "BAD_ARGS");
994
1125
  }
995
1126
  const res = portSet(root, name, repo, service, port);
996
- emit(() => console.log(`${res.repo}.${res.service} -> ${res.port}`), res);
1127
+ emit(
1128
+ () => console.log(
1129
+ `${check()} ${res.repo}${dim(".")}${res.service} ${dim("\u2192")} ${dim(String(res.port))}`
1130
+ ),
1131
+ res
1132
+ );
997
1133
  return;
998
1134
  }
999
1135
  case "unset": {
@@ -1001,17 +1137,34 @@ function workPort(root, name, positionals) {
1001
1137
  const repo = need2(positionals[3], usage);
1002
1138
  const service = need2(positionals[4], usage);
1003
1139
  const res = portUnset(root, name, repo, service);
1004
- emit(() => console.log(`unset ${res.repo}.${res.service} (was ${res.released})`), res);
1140
+ emit(
1141
+ () => console.log(
1142
+ `${check()} unset ${res.repo}${dim(".")}${res.service} ${dim(`(was ${res.released})`)}`
1143
+ ),
1144
+ res
1145
+ );
1005
1146
  return;
1006
1147
  }
1007
1148
  case "ls": {
1008
1149
  const map = portList(root, name);
1009
1150
  emit(() => {
1151
+ const entries = [];
1010
1152
  for (const [repo, ports] of Object.entries(map)) {
1011
1153
  for (const [service, port] of Object.entries(ports)) {
1012
- console.log(`${repo}.${service} -> ${port}`);
1154
+ entries.push({ repo, service, port });
1013
1155
  }
1014
1156
  }
1157
+ if (entries.length === 0) {
1158
+ console.log(dim("no ports allocated yet \u2014 `mx work -n <name> port set <repo> <service>`"));
1159
+ return;
1160
+ }
1161
+ const lhsW = Math.max(...entries.map((e) => `${e.repo}.${e.service}`.length));
1162
+ for (const e of entries) {
1163
+ const lhs = `${e.repo}${dim(".")}${e.service}`;
1164
+ const plain = `${e.repo}.${e.service}`;
1165
+ const pad = " ".repeat(lhsW - plain.length);
1166
+ console.log(`${lhs}${pad} ${dim("\u2192")} ${dim(String(e.port))}`);
1167
+ }
1015
1168
  }, map);
1016
1169
  return;
1017
1170
  }
@@ -1022,13 +1175,14 @@ function workPort(root, name, positionals) {
1022
1175
 
1023
1176
  // src/main.ts
1024
1177
  var VERSION = (() => {
1025
- const here = path7.dirname(fileURLToPath2(import.meta.url));
1026
- const pkg = JSON.parse(readFileSync2(path7.join(here, "..", "package.json"), "utf8"));
1178
+ const here = path8.dirname(fileURLToPath2(import.meta.url));
1179
+ const pkg = JSON.parse(readFileSync2(path8.join(here, "..", "package.json"), "utf8"));
1027
1180
  return pkg.version;
1028
1181
  })();
1029
1182
  function main() {
1030
1183
  const { positionals, flags } = parseArgs(process.argv.slice(2));
1031
1184
  setPorcelain(flags.porcelain);
1185
+ if (positionals[0] === "s" || positionals[0] === "st") positionals[0] = "status";
1032
1186
  try {
1033
1187
  if (flags.version || positionals[0] === "version") {
1034
1188
  emit(() => console.log(`mx ${VERSION}`), { version: VERSION });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roulabs/mx",
3
- "version": "1.2.2",
3
+ "version": "1.7.1",
4
4
  "description": "mx — run several features in parallel across shared repos using git worktrees",
5
5
  "type": "module",
6
6
  "bin": {