@roulabs/mx 1.2.1 → 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.
- package/bin/mx.js +271 -105
- 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
|
|
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) => {
|
|
@@ -167,11 +172,23 @@ function initRuntime(target0, templatesDir2) {
|
|
|
167
172
|
removeStaleRuntimeReadme(target);
|
|
168
173
|
return { runtime: target, created };
|
|
169
174
|
}
|
|
175
|
+
function ensureWorkScaffolding(root, workName) {
|
|
176
|
+
const created = [];
|
|
177
|
+
const sessions = path3.join(workDir(root, workName), "sessions");
|
|
178
|
+
if (!exists(sessions)) {
|
|
179
|
+
fs4.mkdirSync(sessions, { recursive: true });
|
|
180
|
+
created.push(sessions);
|
|
181
|
+
}
|
|
182
|
+
return created;
|
|
183
|
+
}
|
|
170
184
|
function updateRuntime(root, templatesDir2) {
|
|
171
|
-
const
|
|
172
|
-
|
|
185
|
+
const updated = [];
|
|
186
|
+
updated.push(stampClaudeMd(root, templatesDir2));
|
|
173
187
|
const ctxIndex = stampContextIndex(root, templatesDir2);
|
|
174
188
|
if (ctxIndex) updated.push(ctxIndex);
|
|
189
|
+
for (const workName of listWorkNames(root)) {
|
|
190
|
+
updated.push(...ensureWorkScaffolding(root, workName));
|
|
191
|
+
}
|
|
175
192
|
removeStaleRuntimeReadme(root);
|
|
176
193
|
return { runtime: root, updated };
|
|
177
194
|
}
|
|
@@ -309,26 +326,20 @@ function workNew(root, name, description = "") {
|
|
|
309
326
|
const dir = workDir(root, name);
|
|
310
327
|
if (exists(dir)) throw new MxError(`work already exists: ${name}`, "EXISTS");
|
|
311
328
|
fs6.mkdirSync(dir, { recursive: true });
|
|
312
|
-
fs6.mkdirSync(path4.join(dir, "sessions"), { recursive: true });
|
|
313
329
|
const work = { name, description, worktrees: [] };
|
|
314
330
|
writeWork(root, work);
|
|
315
331
|
writeJson(workspaceFile(root, name), { folders: [], settings: {} });
|
|
332
|
+
ensureWorkScaffolding(root, name);
|
|
316
333
|
return { ...work, path: dir };
|
|
317
334
|
}
|
|
318
335
|
function listWorksInfo(root, opts = {}) {
|
|
319
|
-
return listWorkNames(root).map((name) => {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
worktrees: (w.worktrees ?? []).length,
|
|
325
|
-
isArchived: w.isArchived === true,
|
|
326
|
-
archived_at: w.archived_at ?? null
|
|
327
|
-
};
|
|
328
|
-
}).filter((s) => {
|
|
329
|
-
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;
|
|
330
341
|
if (opts.includeArchived) return true;
|
|
331
|
-
return
|
|
342
|
+
return w.isArchived !== true;
|
|
332
343
|
});
|
|
333
344
|
}
|
|
334
345
|
function workInfo(root, name) {
|
|
@@ -555,10 +566,24 @@ function portList(root, name) {
|
|
|
555
566
|
}
|
|
556
567
|
|
|
557
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
|
+
}
|
|
558
580
|
function statusRuntime(root) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
581
|
+
return {
|
|
582
|
+
runtime: root,
|
|
583
|
+
context: { entries: countContextEntries(root) },
|
|
584
|
+
repos: listReposInfo(root),
|
|
585
|
+
works: listWorksInfo(root, { includeArchived: true })
|
|
586
|
+
};
|
|
562
587
|
}
|
|
563
588
|
|
|
564
589
|
// src/args.ts
|
|
@@ -577,7 +602,6 @@ function parseArgs(argv) {
|
|
|
577
602
|
help: false,
|
|
578
603
|
version: false,
|
|
579
604
|
force: false,
|
|
580
|
-
all: false,
|
|
581
605
|
archived: false
|
|
582
606
|
};
|
|
583
607
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -590,8 +614,6 @@ function parseArgs(argv) {
|
|
|
590
614
|
flags.version = true;
|
|
591
615
|
} else if (a === "--force") {
|
|
592
616
|
flags.force = true;
|
|
593
|
-
} else if (a === "--all") {
|
|
594
|
-
flags.all = true;
|
|
595
617
|
} else if (a === "--archived") {
|
|
596
618
|
flags.archived = true;
|
|
597
619
|
} else if (a.startsWith("--") && a.includes("=")) {
|
|
@@ -609,6 +631,22 @@ function parseArgs(argv) {
|
|
|
609
631
|
}
|
|
610
632
|
|
|
611
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
|
+
}
|
|
612
650
|
var porcelain = false;
|
|
613
651
|
function setPorcelain(value) {
|
|
614
652
|
porcelain = value;
|
|
@@ -628,7 +666,9 @@ function fail(err) {
|
|
|
628
666
|
if (porcelain) {
|
|
629
667
|
process.stdout.write(JSON.stringify({ error: message, code }, null, 2) + "\n");
|
|
630
668
|
} else {
|
|
631
|
-
process.stderr.
|
|
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}
|
|
632
672
|
`);
|
|
633
673
|
}
|
|
634
674
|
process.exit(1);
|
|
@@ -639,8 +679,8 @@ var HELP = `mx \u2014 control panel for the mx runtime
|
|
|
639
679
|
|
|
640
680
|
Global:
|
|
641
681
|
mx init [path] scaffold/adopt a runtime (default ~/mx)
|
|
642
|
-
mx status [--porcelain] show runtime, repos, works, ports
|
|
643
|
-
mx update re-
|
|
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
|
|
644
684
|
mx help | version
|
|
645
685
|
|
|
646
686
|
Repos (pristine clones):
|
|
@@ -652,7 +692,7 @@ Repos (pristine clones):
|
|
|
652
692
|
|
|
653
693
|
Works (features):
|
|
654
694
|
mx work new <name> [--description <t>] creates folder + empty work.json + sessions/
|
|
655
|
-
mx work ls [--
|
|
695
|
+
mx work ls [--archived] [--porcelain] default: all works (archived marked); --archived filters to archived only
|
|
656
696
|
mx work -n <name> info [--porcelain]
|
|
657
697
|
mx work -n <name> path print the work folder path (cd "$(mx work -n <name> path)")
|
|
658
698
|
mx work -n <name> describe <text>
|
|
@@ -674,33 +714,33 @@ Runtime discovery: --runtime <path> -> $MX_RUNTIME -> default ~/mx.
|
|
|
674
714
|
`;
|
|
675
715
|
|
|
676
716
|
// src/commands/global.ts
|
|
677
|
-
import * as
|
|
717
|
+
import * as path7 from "path";
|
|
678
718
|
|
|
679
719
|
// src/paths.ts
|
|
680
|
-
import * as
|
|
720
|
+
import * as path6 from "path";
|
|
681
721
|
import { fileURLToPath } from "url";
|
|
682
722
|
function templatesDir() {
|
|
683
723
|
if (process.env.MX_TEMPLATES_DIR) return process.env.MX_TEMPLATES_DIR;
|
|
684
|
-
const here =
|
|
685
|
-
return
|
|
724
|
+
const here = path6.dirname(fileURLToPath(import.meta.url));
|
|
725
|
+
return path6.join(here, "..", "templates");
|
|
686
726
|
}
|
|
687
727
|
|
|
688
728
|
// src/commands/global.ts
|
|
689
729
|
function runtimeEnvHint(runtime) {
|
|
690
|
-
const envRuntime = process.env.MX_RUNTIME ?
|
|
730
|
+
const envRuntime = process.env.MX_RUNTIME ? path7.resolve(process.env.MX_RUNTIME) : null;
|
|
691
731
|
if (envRuntime === runtime) {
|
|
692
|
-
return ["", `$MX_RUNTIME already points here \u2014 you're set
|
|
732
|
+
return ["", `${dim("$MX_RUNTIME already points here \u2014 you're set.")}`];
|
|
693
733
|
}
|
|
694
734
|
if (runtime === defaultRuntime() && !envRuntime) {
|
|
695
|
-
return ["",
|
|
735
|
+
return ["", `${dim("This is the default mx runtime (~/mx) \u2014 no MX_RUNTIME setup needed.")}`];
|
|
696
736
|
}
|
|
697
737
|
return [
|
|
698
738
|
"",
|
|
699
|
-
`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)")}:`,
|
|
700
740
|
"",
|
|
701
|
-
` export MX_RUNTIME="${runtime}"`,
|
|
741
|
+
` ${bold(`export MX_RUNTIME="${runtime}"`)}`,
|
|
702
742
|
"",
|
|
703
|
-
|
|
743
|
+
dim("Without it, future `mx` commands fall back to the default ~/mx.")
|
|
704
744
|
];
|
|
705
745
|
}
|
|
706
746
|
function runGlobal(positionals, flags) {
|
|
@@ -709,8 +749,8 @@ function runGlobal(positionals, flags) {
|
|
|
709
749
|
const target = positionals[1] || discoverRuntime({ runtime: flags.runtime });
|
|
710
750
|
const res = initRuntime(target, templatesDir());
|
|
711
751
|
emit(() => {
|
|
712
|
-
console.log(
|
|
713
|
-
for (const c of res.created) console.log(`
|
|
752
|
+
console.log(`${check()} Runtime ready at ${bold(res.runtime)}`);
|
|
753
|
+
for (const c of res.created) console.log(` ${dim(`+ ${c}`)}`);
|
|
714
754
|
for (const line of runtimeEnvHint(res.runtime)) console.log(line);
|
|
715
755
|
}, res);
|
|
716
756
|
return;
|
|
@@ -718,31 +758,15 @@ function runGlobal(positionals, flags) {
|
|
|
718
758
|
case "status": {
|
|
719
759
|
const root = requireRuntime({ runtime: flags.runtime });
|
|
720
760
|
const data = statusRuntime(root);
|
|
721
|
-
emit(() =>
|
|
722
|
-
console.log(`runtime: ${data.runtime}
|
|
723
|
-
`);
|
|
724
|
-
console.log(`repos (${data.repos.length}):`);
|
|
725
|
-
for (const r of data.repos) {
|
|
726
|
-
console.log(` ${r.name} [${r.branch}] ${r.remote ?? "(no remote)"}`);
|
|
727
|
-
}
|
|
728
|
-
console.log(`
|
|
729
|
-
works (${data.works.length}):`);
|
|
730
|
-
for (const w of data.works) {
|
|
731
|
-
console.log(` ${w.name}${w.description ? ` \u2014 ${w.description}` : ""}`);
|
|
732
|
-
for (const wt of w.worktrees) {
|
|
733
|
-
const ports = Object.entries(wt.ports).map(([s, p]) => `${s}:${p}`).join(", ");
|
|
734
|
-
console.log(` ${wt.repo} [${wt.branch}]${ports ? ` (${ports})` : ""}`);
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}, data);
|
|
761
|
+
emit(() => renderStatus(data), data);
|
|
738
762
|
return;
|
|
739
763
|
}
|
|
740
764
|
case "update": {
|
|
741
765
|
const root = requireRuntime({ runtime: flags.runtime });
|
|
742
766
|
const res = updateRuntime(root, templatesDir());
|
|
743
767
|
emit(() => {
|
|
744
|
-
console.log(
|
|
745
|
-
for (const p of res.updated) console.log(`
|
|
768
|
+
console.log(`${check()} Updated runtime at ${bold(res.runtime)}`);
|
|
769
|
+
for (const p of res.updated) console.log(` ${dim(`+ ${p}`)}`);
|
|
746
770
|
}, res);
|
|
747
771
|
return;
|
|
748
772
|
}
|
|
@@ -750,6 +774,61 @@ works (${data.works.length}):`);
|
|
|
750
774
|
throw new MxError(`unknown command: ${positionals[0]}`, "BAD_ARGS");
|
|
751
775
|
}
|
|
752
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
|
+
}
|
|
753
832
|
|
|
754
833
|
// src/commands/repo.ts
|
|
755
834
|
function need(v, msg) {
|
|
@@ -764,13 +843,24 @@ function dispatchRepo(positionals, flags) {
|
|
|
764
843
|
case "add": {
|
|
765
844
|
const url = need(positionals[2], "usage: mx repo add <git-url> [--name <n>]");
|
|
766
845
|
const res = repoAdd(root, url, flags.name);
|
|
767
|
-
emit(() => console.log(
|
|
846
|
+
emit(() => console.log(`${check()} cloned ${bold(res.name)} ${dim(`\u2192 ${res.path}`)}`), res);
|
|
768
847
|
return;
|
|
769
848
|
}
|
|
770
849
|
case "ls": {
|
|
771
850
|
const repos = listReposInfo(root);
|
|
772
851
|
emit(() => {
|
|
773
|
-
|
|
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
|
+
}
|
|
774
864
|
}, repos);
|
|
775
865
|
return;
|
|
776
866
|
}
|
|
@@ -782,7 +872,7 @@ function dispatchRepo(positionals, flags) {
|
|
|
782
872
|
const res = repoFetch(root, name);
|
|
783
873
|
emit(
|
|
784
874
|
() => console.log(
|
|
785
|
-
|
|
875
|
+
`${check()} fetched ${bold(res.name)} ${dim(`\u2014 ${res.remoteBranches.length} branch(es) on origin, now on ${res.branch}`)}`
|
|
786
876
|
),
|
|
787
877
|
res
|
|
788
878
|
);
|
|
@@ -795,15 +885,12 @@ function dispatchRepo(positionals, flags) {
|
|
|
795
885
|
);
|
|
796
886
|
const res = repoInfo(root, name);
|
|
797
887
|
emit(() => {
|
|
798
|
-
console.log(
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
);
|
|
804
|
-
console.log(
|
|
805
|
-
` used by works: ${res.worktreesInWorks.length ? res.worktreesInWorks.join(", ") : "(none)"}`
|
|
806
|
-
);
|
|
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)}`);
|
|
807
894
|
}, res);
|
|
808
895
|
return;
|
|
809
896
|
}
|
|
@@ -813,7 +900,7 @@ function dispatchRepo(positionals, flags) {
|
|
|
813
900
|
"which repo? pass -n <name> or run inside a repo (mx repo -n <name> rm)"
|
|
814
901
|
);
|
|
815
902
|
const res = repoRemove(root, name);
|
|
816
|
-
emit(() => console.log(
|
|
903
|
+
emit(() => console.log(`${check()} removed repo ${bold(res.name)}`), res);
|
|
817
904
|
return;
|
|
818
905
|
}
|
|
819
906
|
default:
|
|
@@ -833,23 +920,48 @@ function dispatchWork(positionals, flags) {
|
|
|
833
920
|
const name2 = need2(positionals[2], "usage: mx work new <name> [--description <text>]");
|
|
834
921
|
const res = workNew(root2, name2, flags.description ?? "");
|
|
835
922
|
emit(() => {
|
|
836
|
-
console.log(
|
|
837
|
-
console.log(` ${res.path}`);
|
|
923
|
+
console.log(`${check()} created work ${bold(res.name)}`);
|
|
924
|
+
console.log(` ${dim(res.path)}`);
|
|
838
925
|
}, res);
|
|
839
926
|
return;
|
|
840
927
|
}
|
|
841
928
|
if (action === "ls") {
|
|
842
929
|
const root2 = requireRuntime({ runtime: flags.runtime });
|
|
843
930
|
const works = listWorksInfo(root2, {
|
|
844
|
-
includeArchived:
|
|
931
|
+
includeArchived: true,
|
|
845
932
|
onlyArchived: flags.archived
|
|
846
933
|
});
|
|
847
934
|
emit(() => {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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
|
+
}
|
|
853
965
|
}
|
|
854
966
|
}, works);
|
|
855
967
|
return;
|
|
@@ -862,7 +974,19 @@ function dispatchWork(positionals, flags) {
|
|
|
862
974
|
switch (action) {
|
|
863
975
|
case "info": {
|
|
864
976
|
const work = workInfo(root, name);
|
|
865
|
-
emit(() =>
|
|
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);
|
|
866
990
|
return;
|
|
867
991
|
}
|
|
868
992
|
case "path": {
|
|
@@ -873,7 +997,7 @@ function dispatchWork(positionals, flags) {
|
|
|
873
997
|
case "describe": {
|
|
874
998
|
const text = need2(positionals[2], "usage: mx work -n <name> describe <text>");
|
|
875
999
|
const work = workDescribe(root, name, text);
|
|
876
|
-
emit(() => console.log(
|
|
1000
|
+
emit(() => console.log(`${check()} updated description of ${bold(name)}`), work);
|
|
877
1001
|
return;
|
|
878
1002
|
}
|
|
879
1003
|
case "worktree":
|
|
@@ -883,33 +1007,32 @@ function dispatchWork(positionals, flags) {
|
|
|
883
1007
|
case "destroy": {
|
|
884
1008
|
if (flags.force && !flags.porcelain) {
|
|
885
1009
|
process.stderr.write(
|
|
886
|
-
|
|
1010
|
+
`${warn()} ${dim(`permanently removing work "${name}" \u2014 folder and any session summaries will be deleted (branches kept). This cannot be undone.`)}
|
|
887
1011
|
`
|
|
888
1012
|
);
|
|
889
1013
|
}
|
|
890
1014
|
const res = workDestroy(root, name, { force: flags.force });
|
|
891
|
-
emit(
|
|
892
|
-
()
|
|
893
|
-
|
|
894
|
-
)
|
|
895
|
-
|
|
896
|
-
);
|
|
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);
|
|
897
1020
|
return;
|
|
898
1021
|
}
|
|
899
1022
|
case "archive": {
|
|
900
1023
|
if (!flags.porcelain) {
|
|
901
1024
|
process.stderr.write(
|
|
902
|
-
`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.`)}
|
|
903
1026
|
`
|
|
904
1027
|
);
|
|
905
1028
|
}
|
|
906
1029
|
const res = archiveWork(root, name);
|
|
907
|
-
emit(
|
|
908
|
-
()
|
|
909
|
-
|
|
910
|
-
)
|
|
911
|
-
|
|
912
|
-
);
|
|
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);
|
|
913
1036
|
return;
|
|
914
1037
|
}
|
|
915
1038
|
case "unarchive": {
|
|
@@ -926,8 +1049,10 @@ function dispatchWork(positionals, flags) {
|
|
|
926
1049
|
}
|
|
927
1050
|
const res = unarchiveWork(root, name, overrides);
|
|
928
1051
|
emit(() => {
|
|
929
|
-
console.log(
|
|
930
|
-
for (const r of res.restored)
|
|
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
|
+
}
|
|
931
1056
|
}, res);
|
|
932
1057
|
return;
|
|
933
1058
|
}
|
|
@@ -944,15 +1069,28 @@ function workWorktree(root, name, positionals, flags) {
|
|
|
944
1069
|
"usage: mx work -n <name> worktree add <repo> [--branch <b>] [--base <ref>]"
|
|
945
1070
|
);
|
|
946
1071
|
const res = worktreeAdd(root, name, repo, { branch: flags.branch, base: flags.base });
|
|
947
|
-
emit(
|
|
1072
|
+
emit(
|
|
1073
|
+
() => console.log(
|
|
1074
|
+
`${check()} added worktree ${bold(res.repo)} ${dim(`[${res.branch}]`)} ${dim(`\u2192 ${res.path}`)}`
|
|
1075
|
+
),
|
|
1076
|
+
res
|
|
1077
|
+
);
|
|
948
1078
|
return;
|
|
949
1079
|
}
|
|
950
1080
|
case "ls": {
|
|
951
1081
|
const list = worktreeList(root, name);
|
|
952
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));
|
|
953
1088
|
for (const wt of list) {
|
|
954
|
-
const
|
|
955
|
-
|
|
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}`);
|
|
956
1094
|
}
|
|
957
1095
|
}, list);
|
|
958
1096
|
return;
|
|
@@ -960,7 +1098,12 @@ function workWorktree(root, name, positionals, flags) {
|
|
|
960
1098
|
case "rm": {
|
|
961
1099
|
const repo = need2(positionals[3], "usage: mx work -n <name> worktree rm <repo>");
|
|
962
1100
|
const res = worktreeRemove(root, name, repo);
|
|
963
|
-
emit(
|
|
1101
|
+
emit(
|
|
1102
|
+
() => console.log(
|
|
1103
|
+
`${check()} removed worktree ${bold(res.repo)} ${dim(`from ${name} (branch ${res.branch} kept)`)}`
|
|
1104
|
+
),
|
|
1105
|
+
res
|
|
1106
|
+
);
|
|
964
1107
|
return;
|
|
965
1108
|
}
|
|
966
1109
|
default:
|
|
@@ -981,7 +1124,12 @@ function workPort(root, name, positionals) {
|
|
|
981
1124
|
if (!Number.isInteger(port)) throw new MxError(`invalid port: ${portArg}`, "BAD_ARGS");
|
|
982
1125
|
}
|
|
983
1126
|
const res = portSet(root, name, repo, service, port);
|
|
984
|
-
emit(
|
|
1127
|
+
emit(
|
|
1128
|
+
() => console.log(
|
|
1129
|
+
`${check()} ${res.repo}${dim(".")}${res.service} ${dim("\u2192")} ${dim(String(res.port))}`
|
|
1130
|
+
),
|
|
1131
|
+
res
|
|
1132
|
+
);
|
|
985
1133
|
return;
|
|
986
1134
|
}
|
|
987
1135
|
case "unset": {
|
|
@@ -989,17 +1137,34 @@ function workPort(root, name, positionals) {
|
|
|
989
1137
|
const repo = need2(positionals[3], usage);
|
|
990
1138
|
const service = need2(positionals[4], usage);
|
|
991
1139
|
const res = portUnset(root, name, repo, service);
|
|
992
|
-
emit(
|
|
1140
|
+
emit(
|
|
1141
|
+
() => console.log(
|
|
1142
|
+
`${check()} unset ${res.repo}${dim(".")}${res.service} ${dim(`(was ${res.released})`)}`
|
|
1143
|
+
),
|
|
1144
|
+
res
|
|
1145
|
+
);
|
|
993
1146
|
return;
|
|
994
1147
|
}
|
|
995
1148
|
case "ls": {
|
|
996
1149
|
const map = portList(root, name);
|
|
997
1150
|
emit(() => {
|
|
1151
|
+
const entries = [];
|
|
998
1152
|
for (const [repo, ports] of Object.entries(map)) {
|
|
999
1153
|
for (const [service, port] of Object.entries(ports)) {
|
|
1000
|
-
|
|
1154
|
+
entries.push({ repo, service, port });
|
|
1001
1155
|
}
|
|
1002
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
|
+
}
|
|
1003
1168
|
}, map);
|
|
1004
1169
|
return;
|
|
1005
1170
|
}
|
|
@@ -1010,13 +1175,14 @@ function workPort(root, name, positionals) {
|
|
|
1010
1175
|
|
|
1011
1176
|
// src/main.ts
|
|
1012
1177
|
var VERSION = (() => {
|
|
1013
|
-
const here =
|
|
1014
|
-
const pkg = JSON.parse(readFileSync2(
|
|
1178
|
+
const here = path8.dirname(fileURLToPath2(import.meta.url));
|
|
1179
|
+
const pkg = JSON.parse(readFileSync2(path8.join(here, "..", "package.json"), "utf8"));
|
|
1015
1180
|
return pkg.version;
|
|
1016
1181
|
})();
|
|
1017
1182
|
function main() {
|
|
1018
1183
|
const { positionals, flags } = parseArgs(process.argv.slice(2));
|
|
1019
1184
|
setPorcelain(flags.porcelain);
|
|
1185
|
+
if (positionals[0] === "s" || positionals[0] === "st") positionals[0] = "status";
|
|
1020
1186
|
try {
|
|
1021
1187
|
if (flags.version || positionals[0] === "version") {
|
|
1022
1188
|
emit(() => console.log(`mx ${VERSION}`), { version: VERSION });
|