webmux 0.7.2 → 0.7.3
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/README.md +60 -17
- package/backend/dist/server.js +3014 -1438
- package/bin/webmux.js +754 -100
- package/frontend/dist/assets/index-DeLoWUbI.js +32 -0
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
- package/frontend/dist/assets/index-B8KQ-AWX.js +0 -32
package/bin/webmux.js
CHANGED
|
@@ -146,6 +146,12 @@ function W(t, e) {
|
|
|
146
146
|
const s = t;
|
|
147
147
|
s.isTTY && s.setRawMode(e);
|
|
148
148
|
}
|
|
149
|
+
function Bt(t, e, s, i = s) {
|
|
150
|
+
const r = rt(t ?? R);
|
|
151
|
+
return K(e, r - s.length, { hard: true, trim: false }).split(`
|
|
152
|
+
`).map((n, u) => `${u === 0 ? i : s}${n}`).join(`
|
|
153
|
+
`);
|
|
154
|
+
}
|
|
149
155
|
|
|
150
156
|
class B {
|
|
151
157
|
input;
|
|
@@ -431,7 +437,7 @@ var import_sisteransi, at = (t) => t === 161 || t === 164 || t === 167 || t ===
|
|
|
431
437
|
` && (r && p && (i += st(r)), n && (i += it(n))), V += h.length, m = A, A = g.next();
|
|
432
438
|
}
|
|
433
439
|
return i;
|
|
434
|
-
}, At, _, bt, z, rt = (t) => ("columns" in t) && typeof t.columns == "number" ? t.columns : 80, nt = (t) => ("rows" in t) && typeof t.rows == "number" ? t.rows : 20, Vt, kt, yt;
|
|
440
|
+
}, At, _, bt, z, rt = (t) => ("columns" in t) && typeof t.columns == "number" ? t.columns : 80, nt = (t) => ("rows" in t) && typeof t.rows == "number" ? t.rows : 20, Vt, kt, yt, Tt;
|
|
435
441
|
var init_dist = __esm(() => {
|
|
436
442
|
import_sisteransi = __toESM(require_src(), 1);
|
|
437
443
|
O = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/y;
|
|
@@ -573,6 +579,33 @@ var init_dist = __esm(() => {
|
|
|
573
579
|
});
|
|
574
580
|
}
|
|
575
581
|
};
|
|
582
|
+
Tt = class Tt extends B {
|
|
583
|
+
options;
|
|
584
|
+
cursor = 0;
|
|
585
|
+
get _selectedValue() {
|
|
586
|
+
return this.options[this.cursor];
|
|
587
|
+
}
|
|
588
|
+
changeValue() {
|
|
589
|
+
this.value = this._selectedValue.value;
|
|
590
|
+
}
|
|
591
|
+
constructor(e) {
|
|
592
|
+
super(e, false), this.options = e.options;
|
|
593
|
+
const s = this.options.findIndex(({ value: r }) => r === e.initialValue), i = s === -1 ? 0 : s;
|
|
594
|
+
this.cursor = this.options[i].disabled ? x(i, 1, this.options) : i, this.changeValue(), this.on("cursor", (r) => {
|
|
595
|
+
switch (r) {
|
|
596
|
+
case "left":
|
|
597
|
+
case "up":
|
|
598
|
+
this.cursor = x(this.cursor, -1, this.options);
|
|
599
|
+
break;
|
|
600
|
+
case "down":
|
|
601
|
+
case "right":
|
|
602
|
+
this.cursor = x(this.cursor, 1, this.options);
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
this.changeValue();
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
};
|
|
576
609
|
});
|
|
577
610
|
|
|
578
611
|
// node_modules/.bun/@clack+prompts@1.1.0/node_modules/@clack/prompts/dist/index.mjs
|
|
@@ -600,6 +633,18 @@ var import_sisteransi2, ee, I2 = (e, r) => ee ? e : r, Re, $e, de, V, he, h, x2,
|
|
|
600
633
|
case "submit":
|
|
601
634
|
return t("green", V);
|
|
602
635
|
}
|
|
636
|
+
}, ve = (e) => {
|
|
637
|
+
switch (e) {
|
|
638
|
+
case "initial":
|
|
639
|
+
case "active":
|
|
640
|
+
return t("cyan", h);
|
|
641
|
+
case "cancel":
|
|
642
|
+
return t("red", h);
|
|
643
|
+
case "error":
|
|
644
|
+
return t("yellow", h);
|
|
645
|
+
case "submit":
|
|
646
|
+
return t("green", h);
|
|
647
|
+
}
|
|
603
648
|
}, mt2 = (e) => e === 161 || e === 164 || e === 167 || e === 168 || e === 170 || e === 173 || e === 174 || e >= 176 && e <= 180 || e >= 182 && e <= 186 || e >= 188 && e <= 191 || e === 198 || e === 208 || e === 215 || e === 216 || e >= 222 && e <= 225 || e === 230 || e >= 232 && e <= 234 || e === 236 || e === 237 || e === 240 || e === 242 || e === 243 || e >= 247 && e <= 250 || e === 252 || e === 254 || e === 257 || e === 273 || e === 275 || e === 283 || e === 294 || e === 295 || e === 299 || e >= 305 && e <= 307 || e === 312 || e >= 319 && e <= 322 || e === 324 || e >= 328 && e <= 331 || e === 333 || e === 338 || e === 339 || e === 358 || e === 359 || e === 363 || e === 462 || e === 464 || e === 466 || e === 468 || e === 470 || e === 472 || e === 474 || e === 476 || e === 593 || e === 609 || e === 708 || e === 711 || e >= 713 && e <= 715 || e === 717 || e === 720 || e >= 728 && e <= 731 || e === 733 || e === 735 || e >= 768 && e <= 879 || e >= 913 && e <= 929 || e >= 931 && e <= 937 || e >= 945 && e <= 961 || e >= 963 && e <= 969 || e === 1025 || e >= 1040 && e <= 1103 || e === 1105 || e === 8208 || e >= 8211 && e <= 8214 || e === 8216 || e === 8217 || e === 8220 || e === 8221 || e >= 8224 && e <= 8226 || e >= 8228 && e <= 8231 || e === 8240 || e === 8242 || e === 8243 || e === 8245 || e === 8251 || e === 8254 || e === 8308 || e === 8319 || e >= 8321 && e <= 8324 || e === 8364 || e === 8451 || e === 8453 || e === 8457 || e === 8467 || e === 8470 || e === 8481 || e === 8482 || e === 8486 || e === 8491 || e === 8531 || e === 8532 || e >= 8539 && e <= 8542 || e >= 8544 && e <= 8555 || e >= 8560 && e <= 8569 || e === 8585 || e >= 8592 && e <= 8601 || e === 8632 || e === 8633 || e === 8658 || e === 8660 || e === 8679 || e === 8704 || e === 8706 || e === 8707 || e === 8711 || e === 8712 || e === 8715 || e === 8719 || e === 8721 || e === 8725 || e === 8730 || e >= 8733 && e <= 8736 || e === 8739 || e === 8741 || e >= 8743 && e <= 8748 || e === 8750 || e >= 8756 && e <= 8759 || e === 8764 || e === 8765 || e === 8776 || e === 8780 || e === 8786 || e === 8800 || e === 8801 || e >= 8804 && e <= 8807 || e === 8810 || e === 8811 || e === 8814 || e === 8815 || e === 8834 || e === 8835 || e === 8838 || e === 8839 || e === 8853 || e === 8857 || e === 8869 || e === 8895 || e === 8978 || e >= 9312 && e <= 9449 || e >= 9451 && e <= 9547 || e >= 9552 && e <= 9587 || e >= 9600 && e <= 9615 || e >= 9618 && e <= 9621 || e === 9632 || e === 9633 || e >= 9635 && e <= 9641 || e === 9650 || e === 9651 || e === 9654 || e === 9655 || e === 9660 || e === 9661 || e === 9664 || e === 9665 || e >= 9670 && e <= 9672 || e === 9675 || e >= 9678 && e <= 9681 || e >= 9698 && e <= 9701 || e === 9711 || e === 9733 || e === 9734 || e === 9737 || e === 9742 || e === 9743 || e === 9756 || e === 9758 || e === 9792 || e === 9794 || e === 9824 || e === 9825 || e >= 9827 && e <= 9829 || e >= 9831 && e <= 9834 || e === 9836 || e === 9837 || e === 9839 || e === 9886 || e === 9887 || e === 9919 || e >= 9926 && e <= 9933 || e >= 9935 && e <= 9939 || e >= 9941 && e <= 9953 || e === 9955 || e === 9960 || e === 9961 || e >= 9963 && e <= 9969 || e === 9972 || e >= 9974 && e <= 9977 || e === 9979 || e === 9980 || e === 9982 || e === 9983 || e === 10045 || e >= 10102 && e <= 10111 || e >= 11094 && e <= 11097 || e >= 12872 && e <= 12879 || e >= 57344 && e <= 63743 || e >= 65024 && e <= 65039 || e === 65533 || e >= 127232 && e <= 127242 || e >= 127248 && e <= 127277 || e >= 127280 && e <= 127337 || e >= 127344 && e <= 127373 || e === 127375 || e === 127376 || e >= 127387 && e <= 127404 || e >= 917760 && e <= 917999 || e >= 983040 && e <= 1048573 || e >= 1048576 && e <= 1114109, gt2 = (e) => e === 12288 || e >= 65281 && e <= 65376 || e >= 65504 && e <= 65510, ft2 = (e) => e >= 4352 && e <= 4447 || e === 8986 || e === 8987 || e === 9001 || e === 9002 || e >= 9193 && e <= 9196 || e === 9200 || e === 9203 || e === 9725 || e === 9726 || e === 9748 || e === 9749 || e >= 9800 && e <= 9811 || e === 9855 || e === 9875 || e === 9889 || e === 9898 || e === 9899 || e === 9917 || e === 9918 || e === 9924 || e === 9925 || e === 9934 || e === 9940 || e === 9962 || e === 9970 || e === 9971 || e === 9973 || e === 9978 || e === 9981 || e === 9989 || e === 9994 || e === 9995 || e === 10024 || e === 10060 || e === 10062 || e >= 10067 && e <= 10069 || e === 10071 || e >= 10133 && e <= 10135 || e === 10160 || e === 10175 || e === 11035 || e === 11036 || e === 11088 || e === 11093 || e >= 11904 && e <= 11929 || e >= 11931 && e <= 12019 || e >= 12032 && e <= 12245 || e >= 12272 && e <= 12287 || e >= 12289 && e <= 12350 || e >= 12353 && e <= 12438 || e >= 12441 && e <= 12543 || e >= 12549 && e <= 12591 || e >= 12593 && e <= 12686 || e >= 12688 && e <= 12771 || e >= 12783 && e <= 12830 || e >= 12832 && e <= 12871 || e >= 12880 && e <= 19903 || e >= 19968 && e <= 42124 || e >= 42128 && e <= 42182 || e >= 43360 && e <= 43388 || e >= 44032 && e <= 55203 || e >= 63744 && e <= 64255 || e >= 65040 && e <= 65049 || e >= 65072 && e <= 65106 || e >= 65108 && e <= 65126 || e >= 65128 && e <= 65131 || e >= 94176 && e <= 94180 || e === 94192 || e === 94193 || e >= 94208 && e <= 100343 || e >= 100352 && e <= 101589 || e >= 101632 && e <= 101640 || e >= 110576 && e <= 110579 || e >= 110581 && e <= 110587 || e === 110589 || e === 110590 || e >= 110592 && e <= 110882 || e === 110898 || e >= 110928 && e <= 110930 || e === 110933 || e >= 110948 && e <= 110951 || e >= 110960 && e <= 111355 || e === 126980 || e === 127183 || e === 127374 || e >= 127377 && e <= 127386 || e >= 127488 && e <= 127490 || e >= 127504 && e <= 127547 || e >= 127552 && e <= 127560 || e === 127568 || e === 127569 || e >= 127584 && e <= 127589 || e >= 127744 && e <= 127776 || e >= 127789 && e <= 127797 || e >= 127799 && e <= 127868 || e >= 127870 && e <= 127891 || e >= 127904 && e <= 127946 || e >= 127951 && e <= 127955 || e >= 127968 && e <= 127984 || e === 127988 || e >= 127992 && e <= 128062 || e === 128064 || e >= 128066 && e <= 128252 || e >= 128255 && e <= 128317 || e >= 128331 && e <= 128334 || e >= 128336 && e <= 128359 || e === 128378 || e === 128405 || e === 128406 || e === 128420 || e >= 128507 && e <= 128591 || e >= 128640 && e <= 128709 || e === 128716 || e >= 128720 && e <= 128722 || e >= 128725 && e <= 128727 || e >= 128732 && e <= 128735 || e === 128747 || e === 128748 || e >= 128756 && e <= 128764 || e >= 128992 && e <= 129003 || e === 129008 || e >= 129292 && e <= 129338 || e >= 129340 && e <= 129349 || e >= 129351 && e <= 129535 || e >= 129648 && e <= 129660 || e >= 129664 && e <= 129672 || e >= 129680 && e <= 129725 || e >= 129727 && e <= 129733 || e >= 129742 && e <= 129755 || e >= 129760 && e <= 129768 || e >= 129776 && e <= 129784 || e >= 131072 && e <= 196605 || e >= 196608 && e <= 262141, we, re, ie, Ae, ne, Ft2, yt2, Le = (e, r = {}, s = {}) => {
|
|
604
649
|
const i = r.limit ?? 1 / 0, a = r.ellipsis ?? "", o = r?.ellipsisWidth ?? (a ? Le(a, yt2, s).width : 0), u = s.ansiWidth ?? 0, l = s.controlWidth ?? 0, n = s.tabWidth ?? 8, c = s.ambiguousWidth ?? 1, p = s.emojiWidth ?? 2, f = s.fullWidthWidth ?? 2, g = s.regularWidth ?? 1, E = s.wideWidth ?? 2;
|
|
605
650
|
let $ = 0, m = 0, d = e.length, F = 0, y2 = false, v = d, C = Math.max(0, i - o), A = 0, b = 0, w = 0, S2 = 0;
|
|
@@ -744,6 +789,39 @@ var import_sisteransi2, ee, I2 = (e, r) => ee ? e : r, Re, $e, de, V, he, h, x2,
|
|
|
744
789
|
` && (a && d && (i += Ue(a)), o && (i += Ke(o))), E += $.length, f = g, g = p.next();
|
|
745
790
|
}
|
|
746
791
|
return i;
|
|
792
|
+
}, bt2 = (e, r, s, i, a) => {
|
|
793
|
+
let o = r, u = 0;
|
|
794
|
+
for (let l = s;l < i; l++) {
|
|
795
|
+
const n = e[l];
|
|
796
|
+
if (o = o - n.length, u++, o <= a)
|
|
797
|
+
break;
|
|
798
|
+
}
|
|
799
|
+
return { lineCount: o, removals: u };
|
|
800
|
+
}, X2 = ({ cursor: e, options: r, style: s, output: i = process.stdout, maxItems: a = Number.POSITIVE_INFINITY, columnPadding: o = 0, rowPadding: u = 4 }) => {
|
|
801
|
+
const l = rt(i) - o, n = nt(i), c = t("dim", "..."), p = Math.max(n - u, 0), f = Math.max(Math.min(a, p), 5);
|
|
802
|
+
let g = 0;
|
|
803
|
+
e >= f - 3 && (g = Math.max(Math.min(e - f + 3, r.length - f), 0));
|
|
804
|
+
let E = f < r.length && g > 0, $ = f < r.length && g + f < r.length;
|
|
805
|
+
const m = Math.min(g + f, r.length), d = [];
|
|
806
|
+
let F = 0;
|
|
807
|
+
E && F++, $ && F++;
|
|
808
|
+
const y2 = g + (E ? 1 : 0), v = m - ($ ? 1 : 0);
|
|
809
|
+
for (let A = y2;A < v; A++) {
|
|
810
|
+
const b = J(s(r[A], A === e), l, { hard: true, trim: false }).split(`
|
|
811
|
+
`);
|
|
812
|
+
d.push(b), F += b.length;
|
|
813
|
+
}
|
|
814
|
+
if (F > p) {
|
|
815
|
+
let A = 0, b = 0, w = F;
|
|
816
|
+
const S2 = e - y2, T2 = (M2, O2) => bt2(d, w, M2, O2, p);
|
|
817
|
+
E ? ({ lineCount: w, removals: A } = T2(0, S2), w > p && ({ lineCount: w, removals: b } = T2(S2 + 1, d.length))) : ({ lineCount: w, removals: b } = T2(S2 + 1, d.length), w > p && ({ lineCount: w, removals: A } = T2(0, S2))), A > 0 && (E = true, d.splice(0, A)), b > 0 && ($ = true, d.splice(d.length - b, b));
|
|
818
|
+
}
|
|
819
|
+
const C = [];
|
|
820
|
+
E && C.push(c);
|
|
821
|
+
for (const A of d)
|
|
822
|
+
for (const b of A)
|
|
823
|
+
C.push(b);
|
|
824
|
+
return $ && C.push(c), C;
|
|
747
825
|
}, Rt = (e) => {
|
|
748
826
|
const r = e.active ?? "Yes", s = e.inactive ?? "No";
|
|
749
827
|
return new kt({ active: r, inactive: s, signal: e.signal, input: e.input, output: e.output, initialValue: e.initialValue ?? true, render() {
|
|
@@ -796,7 +874,50 @@ ${t("gray", x2)} ` : "";
|
|
|
796
874
|
${c}
|
|
797
875
|
${t("gray", f + se.repeat(n + 2) + me)}
|
|
798
876
|
`);
|
|
799
|
-
}, ze,
|
|
877
|
+
}, ze, oe = (e, r) => e.includes(`
|
|
878
|
+
`) ? e.split(`
|
|
879
|
+
`).map((s) => r(s)).join(`
|
|
880
|
+
`) : r(e), Jt = (e) => {
|
|
881
|
+
const r = (s, i) => {
|
|
882
|
+
const a = s.label ?? String(s.value);
|
|
883
|
+
switch (i) {
|
|
884
|
+
case "disabled":
|
|
885
|
+
return `${t("gray", H2)} ${oe(a, (o) => t("gray", o))}${s.hint ? ` ${t("dim", `(${s.hint ?? "disabled"})`)}` : ""}`;
|
|
886
|
+
case "selected":
|
|
887
|
+
return `${oe(a, (o) => t("dim", o))}`;
|
|
888
|
+
case "active":
|
|
889
|
+
return `${t("green", z2)} ${a}${s.hint ? ` ${t("dim", `(${s.hint})`)}` : ""}`;
|
|
890
|
+
case "cancelled":
|
|
891
|
+
return `${oe(a, (o) => t(["strikethrough", "dim"], o))}`;
|
|
892
|
+
default:
|
|
893
|
+
return `${t("dim", H2)} ${oe(a, (o) => t("dim", o))}`;
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
return new Tt({ options: e.options, signal: e.signal, input: e.input, output: e.output, initialValue: e.initialValue, render() {
|
|
897
|
+
const s = e.withGuide ?? _.withGuide, i = `${W2(this.state)} `, a = `${ve(this.state)} `, o = Bt(e.output, e.message, a, i), u = `${s ? `${t("gray", h)}
|
|
898
|
+
` : ""}${o}
|
|
899
|
+
`;
|
|
900
|
+
switch (this.state) {
|
|
901
|
+
case "submit": {
|
|
902
|
+
const l = s ? `${t("gray", h)} ` : "", n = Bt(e.output, r(this.options[this.cursor], "selected"), l);
|
|
903
|
+
return `${u}${n}`;
|
|
904
|
+
}
|
|
905
|
+
case "cancel": {
|
|
906
|
+
const l = s ? `${t("gray", h)} ` : "", n = Bt(e.output, r(this.options[this.cursor], "cancelled"), l);
|
|
907
|
+
return `${u}${n}${s ? `
|
|
908
|
+
${t("gray", h)}` : ""}`;
|
|
909
|
+
}
|
|
910
|
+
default: {
|
|
911
|
+
const l = s ? `${t("cyan", h)} ` : "", n = s ? t("cyan", x2) : "", c = u.split(`
|
|
912
|
+
`).length, p = s ? 2 : 1;
|
|
913
|
+
return `${u}${l}${X2({ output: e.output, cursor: this.cursor, options: this.options, maxItems: e.maxItems, columnPadding: l.length, rowPadding: c + p, style: (f, g) => r(f, f.disabled ? "disabled" : g ? "active" : "inactive") }).join(`
|
|
914
|
+
${l}`)}
|
|
915
|
+
${n}
|
|
916
|
+
`;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
} }).prompt();
|
|
920
|
+
}, Qe;
|
|
800
921
|
var init_dist2 = __esm(() => {
|
|
801
922
|
init_dist();
|
|
802
923
|
init_dist();
|
|
@@ -902,10 +1023,467 @@ function detectProjectName(gitRoot) {
|
|
|
902
1023
|
}
|
|
903
1024
|
var init_shared = () => {};
|
|
904
1025
|
|
|
1026
|
+
// bin/src/init-helpers.ts
|
|
1027
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
|
|
1028
|
+
import { tmpdir } from "os";
|
|
1029
|
+
import { join as join2 } from "path";
|
|
1030
|
+
function isRecord(value) {
|
|
1031
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1032
|
+
}
|
|
1033
|
+
function readString(value) {
|
|
1034
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
1035
|
+
}
|
|
1036
|
+
function detectPackageManager(gitRoot) {
|
|
1037
|
+
if (existsSync2(join2(gitRoot, "bun.lock")) || existsSync2(join2(gitRoot, "bun.lockb")))
|
|
1038
|
+
return "bun";
|
|
1039
|
+
if (existsSync2(join2(gitRoot, "pnpm-lock.yaml")))
|
|
1040
|
+
return "pnpm";
|
|
1041
|
+
if (existsSync2(join2(gitRoot, "yarn.lock")))
|
|
1042
|
+
return "yarn";
|
|
1043
|
+
return "npm";
|
|
1044
|
+
}
|
|
1045
|
+
function detectMainBranch(gitRoot) {
|
|
1046
|
+
const originHead = run("git", ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"], { cwd: gitRoot });
|
|
1047
|
+
if (originHead.success) {
|
|
1048
|
+
const branch = originHead.stdout.toString().trim().split("/").pop();
|
|
1049
|
+
if (branch)
|
|
1050
|
+
return branch;
|
|
1051
|
+
}
|
|
1052
|
+
const mainBranch = run("git", ["branch", "--list", "main"], { cwd: gitRoot });
|
|
1053
|
+
if (mainBranch.success && mainBranch.stdout.toString().trim())
|
|
1054
|
+
return "main";
|
|
1055
|
+
const masterBranch = run("git", ["branch", "--list", "master"], { cwd: gitRoot });
|
|
1056
|
+
if (masterBranch.success && masterBranch.stdout.toString().trim())
|
|
1057
|
+
return "master";
|
|
1058
|
+
const currentBranch = run("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
|
|
1059
|
+
if (currentBranch.success) {
|
|
1060
|
+
const branch = currentBranch.stdout.toString().trim();
|
|
1061
|
+
if (branch && branch !== "HEAD")
|
|
1062
|
+
return branch;
|
|
1063
|
+
}
|
|
1064
|
+
return "main";
|
|
1065
|
+
}
|
|
1066
|
+
function buildRunScriptCommand(packageManager, scriptName) {
|
|
1067
|
+
if (packageManager === "bun")
|
|
1068
|
+
return `bun run ${scriptName}`;
|
|
1069
|
+
if (packageManager === "pnpm")
|
|
1070
|
+
return `pnpm ${scriptName}`;
|
|
1071
|
+
if (packageManager === "yarn")
|
|
1072
|
+
return `yarn ${scriptName}`;
|
|
1073
|
+
return `npm run ${scriptName}`;
|
|
1074
|
+
}
|
|
1075
|
+
function detectInitProjectContext(gitRoot, defaultAgent) {
|
|
1076
|
+
return {
|
|
1077
|
+
gitRoot,
|
|
1078
|
+
projectName: detectProjectName(gitRoot),
|
|
1079
|
+
mainBranch: detectMainBranch(gitRoot),
|
|
1080
|
+
defaultAgent,
|
|
1081
|
+
packageManager: detectPackageManager(gitRoot)
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function buildInitPromptSpec(context) {
|
|
1085
|
+
const systemPrompt = [
|
|
1086
|
+
"You are bootstrapping a local repository for webmux.",
|
|
1087
|
+
"A starter `.webmux.yaml` already exists at the repo root.",
|
|
1088
|
+
"Inspect the repository in the current working directory and edit that existing `.webmux.yaml` in place.",
|
|
1089
|
+
"Do not modify any other file.",
|
|
1090
|
+
"Do not ask the user questions. Infer the config from the repository contents.",
|
|
1091
|
+
"Be efficient: inspect only the files needed to determine the project name, main branch, service layout, dev commands, and ports.",
|
|
1092
|
+
"The YAML must be valid and minimal.",
|
|
1093
|
+
`Set workspace.defaultAgent to ${context.defaultAgent}.`,
|
|
1094
|
+
"Use this config shape:",
|
|
1095
|
+
"name: infer from the repository",
|
|
1096
|
+
"workspace.mainBranch: infer from git",
|
|
1097
|
+
"workspace.worktreeRoot: keep __worktrees unless there is clear evidence of an existing alternative",
|
|
1098
|
+
"services: one entry per real dev service with name, portEnv, and portStart when a default port is clear",
|
|
1099
|
+
"profiles.default.runtime: host",
|
|
1100
|
+
"profiles.default.envPassthrough: []",
|
|
1101
|
+
"profiles.default.panes: start with an agent pane focused true, then add command panes for real services",
|
|
1102
|
+
"Command panes should use the repository's real dev command and pass the relevant port env var into the command when needed.",
|
|
1103
|
+
"Use split: right for the first command pane and split: bottom for later command panes.",
|
|
1104
|
+
"Include integrations.github.linkedRepos as an empty list, integrations.linear.enabled as true, and startupEnvs as an empty object.",
|
|
1105
|
+
"Only include optional sections like auto_name, lifecycleHooks, sandbox/docker config, mounts, or systemPrompt if the repository gives clear evidence they are needed.",
|
|
1106
|
+
"Prefer editing the existing keys over replacing the file with a completely different shape.",
|
|
1107
|
+
"Before finishing, verify that `.webmux.yaml` exists and contains the final YAML."
|
|
1108
|
+
].join(`
|
|
1109
|
+
`);
|
|
1110
|
+
return {
|
|
1111
|
+
systemPrompt,
|
|
1112
|
+
userPrompt: "Adapt the existing starter `.webmux.yaml` for this repository."
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
function buildInitAgentCommand(agent, prompt, outputPrefix = "webmux-init") {
|
|
1116
|
+
if (agent === "claude") {
|
|
1117
|
+
return {
|
|
1118
|
+
agent,
|
|
1119
|
+
cmd: "claude",
|
|
1120
|
+
args: [
|
|
1121
|
+
"-p",
|
|
1122
|
+
"--verbose",
|
|
1123
|
+
"--permission-mode",
|
|
1124
|
+
"bypassPermissions",
|
|
1125
|
+
"--model",
|
|
1126
|
+
FAST_CLAUDE_MODEL,
|
|
1127
|
+
"--effort",
|
|
1128
|
+
FAST_CLAUDE_EFFORT,
|
|
1129
|
+
"--output-format",
|
|
1130
|
+
"stream-json",
|
|
1131
|
+
"--include-partial-messages",
|
|
1132
|
+
"--append-system-prompt",
|
|
1133
|
+
prompt.systemPrompt,
|
|
1134
|
+
prompt.userPrompt
|
|
1135
|
+
]
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
const summaryPath = join2(tmpdir(), `${outputPrefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.txt`);
|
|
1139
|
+
return {
|
|
1140
|
+
agent,
|
|
1141
|
+
cmd: "codex",
|
|
1142
|
+
args: [
|
|
1143
|
+
"exec",
|
|
1144
|
+
"--sandbox",
|
|
1145
|
+
"workspace-write",
|
|
1146
|
+
"--color",
|
|
1147
|
+
"never",
|
|
1148
|
+
"--json",
|
|
1149
|
+
"-m",
|
|
1150
|
+
FAST_CODEX_MODEL,
|
|
1151
|
+
"-o",
|
|
1152
|
+
summaryPath,
|
|
1153
|
+
"-c",
|
|
1154
|
+
`model_reasoning_effort="${FAST_CODEX_REASONING}"`,
|
|
1155
|
+
"-c",
|
|
1156
|
+
`developer_instructions=${prompt.systemPrompt}`,
|
|
1157
|
+
prompt.userPrompt
|
|
1158
|
+
],
|
|
1159
|
+
summaryPath
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
function closeAssistant(state) {
|
|
1163
|
+
if (!state.assistantSnapshot)
|
|
1164
|
+
return [];
|
|
1165
|
+
state.assistantSnapshot = "";
|
|
1166
|
+
return [{ kind: "assistant_done", text: "" }];
|
|
1167
|
+
}
|
|
1168
|
+
function streamSnapshot(state, snapshot) {
|
|
1169
|
+
if (!snapshot)
|
|
1170
|
+
return [];
|
|
1171
|
+
if (!state.assistantSnapshot) {
|
|
1172
|
+
state.assistantSnapshot = snapshot;
|
|
1173
|
+
return [{ kind: "assistant_delta", text: snapshot }];
|
|
1174
|
+
}
|
|
1175
|
+
if (snapshot === state.assistantSnapshot)
|
|
1176
|
+
return [];
|
|
1177
|
+
if (snapshot.startsWith(state.assistantSnapshot)) {
|
|
1178
|
+
const delta = snapshot.slice(state.assistantSnapshot.length);
|
|
1179
|
+
state.assistantSnapshot = snapshot;
|
|
1180
|
+
return delta ? [{ kind: "assistant_delta", text: delta }] : [];
|
|
1181
|
+
}
|
|
1182
|
+
state.assistantSnapshot = snapshot;
|
|
1183
|
+
return [
|
|
1184
|
+
{ kind: "assistant_done", text: "" },
|
|
1185
|
+
{ kind: "assistant_delta", text: snapshot }
|
|
1186
|
+
];
|
|
1187
|
+
}
|
|
1188
|
+
function emitStatus(state, text) {
|
|
1189
|
+
const status = text?.trim();
|
|
1190
|
+
if (!status || status === state.lastStatus)
|
|
1191
|
+
return [];
|
|
1192
|
+
state.lastStatus = status;
|
|
1193
|
+
return [...closeAssistant(state), { kind: "status", text: status }];
|
|
1194
|
+
}
|
|
1195
|
+
function emitWarning(state, text) {
|
|
1196
|
+
const warning = text?.trim();
|
|
1197
|
+
if (!warning)
|
|
1198
|
+
return [];
|
|
1199
|
+
return [...closeAssistant(state), { kind: "warning", text: warning }];
|
|
1200
|
+
}
|
|
1201
|
+
function extractTextBlocks(value) {
|
|
1202
|
+
if (typeof value === "string")
|
|
1203
|
+
return value;
|
|
1204
|
+
if (Array.isArray(value)) {
|
|
1205
|
+
return value.map((entry) => extractTextBlocks(entry)).filter((entry) => entry.length > 0).join("");
|
|
1206
|
+
}
|
|
1207
|
+
if (!isRecord(value))
|
|
1208
|
+
return "";
|
|
1209
|
+
if (value.type === "text" && typeof value.text === "string") {
|
|
1210
|
+
return value.text;
|
|
1211
|
+
}
|
|
1212
|
+
if (typeof value.output_text === "string")
|
|
1213
|
+
return value.output_text;
|
|
1214
|
+
if (typeof value.text === "string")
|
|
1215
|
+
return value.text;
|
|
1216
|
+
if (typeof value.delta === "string")
|
|
1217
|
+
return value.delta;
|
|
1218
|
+
if (Array.isArray(value.content))
|
|
1219
|
+
return extractTextBlocks(value.content);
|
|
1220
|
+
if (Array.isArray(value.contents))
|
|
1221
|
+
return extractTextBlocks(value.contents);
|
|
1222
|
+
if (Array.isArray(value.parts))
|
|
1223
|
+
return extractTextBlocks(value.parts);
|
|
1224
|
+
if (Array.isArray(value.output))
|
|
1225
|
+
return extractTextBlocks(value.output);
|
|
1226
|
+
return "";
|
|
1227
|
+
}
|
|
1228
|
+
function extractCommandText(value) {
|
|
1229
|
+
if (typeof value === "string" && value.trim().length > 0)
|
|
1230
|
+
return value.trim();
|
|
1231
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
1232
|
+
return value.join(" ").trim() || null;
|
|
1233
|
+
}
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
function truncateText(text, limit = 120) {
|
|
1237
|
+
return text.length > limit ? `${text.slice(0, limit - 3)}...` : text;
|
|
1238
|
+
}
|
|
1239
|
+
function extractStructuredMessage(raw) {
|
|
1240
|
+
const candidates = [
|
|
1241
|
+
raw.message,
|
|
1242
|
+
raw.result,
|
|
1243
|
+
raw.item,
|
|
1244
|
+
raw.output,
|
|
1245
|
+
raw.content,
|
|
1246
|
+
raw.text,
|
|
1247
|
+
raw.response
|
|
1248
|
+
];
|
|
1249
|
+
for (const candidate of candidates) {
|
|
1250
|
+
const text = extractTextBlocks(candidate).trim();
|
|
1251
|
+
if (text)
|
|
1252
|
+
return text;
|
|
1253
|
+
}
|
|
1254
|
+
return null;
|
|
1255
|
+
}
|
|
1256
|
+
function parseClaudeStreamLine(raw, state) {
|
|
1257
|
+
const type = readString(raw.type) ?? "unknown";
|
|
1258
|
+
if (type === "content_block_delta" && isRecord(raw.delta)) {
|
|
1259
|
+
if (raw.delta.type === "text_delta" && typeof raw.delta.text === "string") {
|
|
1260
|
+
return [{ kind: "assistant_delta", text: raw.delta.text }];
|
|
1261
|
+
}
|
|
1262
|
+
if (typeof raw.delta.text === "string") {
|
|
1263
|
+
return [{ kind: "assistant_delta", text: raw.delta.text }];
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (type === "content_block_start" && isRecord(raw.content_block)) {
|
|
1267
|
+
if (raw.content_block.type === "tool_use") {
|
|
1268
|
+
return emitStatus(state, `Using ${readString(raw.content_block.name) ?? "tool"}...`);
|
|
1269
|
+
}
|
|
1270
|
+
const snapshot2 = extractTextBlocks(raw.content_block).trim();
|
|
1271
|
+
return streamSnapshot(state, snapshot2);
|
|
1272
|
+
}
|
|
1273
|
+
if (type === "message_stop" || type === "result_stop" || type === "content_block_stop") {
|
|
1274
|
+
return closeAssistant(state);
|
|
1275
|
+
}
|
|
1276
|
+
if (type === "error") {
|
|
1277
|
+
const message = isRecord(raw.error) ? readString(raw.error.message) : readString(raw.message);
|
|
1278
|
+
return emitWarning(state, message ?? "Claude returned an error.");
|
|
1279
|
+
}
|
|
1280
|
+
if (type.includes("tool")) {
|
|
1281
|
+
const toolName = readString(raw.tool_name) ?? (isRecord(raw.tool) ? readString(raw.tool.name) : null) ?? readString(raw.name);
|
|
1282
|
+
if (toolName)
|
|
1283
|
+
return emitStatus(state, `Using ${toolName}...`);
|
|
1284
|
+
}
|
|
1285
|
+
const snapshot = extractStructuredMessage(raw);
|
|
1286
|
+
if (snapshot)
|
|
1287
|
+
return streamSnapshot(state, snapshot);
|
|
1288
|
+
return [];
|
|
1289
|
+
}
|
|
1290
|
+
function parseCodexStatus(raw, type) {
|
|
1291
|
+
const command = extractCommandText(raw.command) ?? (isRecord(raw.item) ? extractCommandText(raw.item.command) : null);
|
|
1292
|
+
if (command && (type.includes("command") || type.includes("exec") || type.includes("shell"))) {
|
|
1293
|
+
return `Running ${truncateText(command)}`;
|
|
1294
|
+
}
|
|
1295
|
+
const toolName = readString(raw.tool_name) ?? readString(raw.name) ?? (isRecord(raw.item) ? readString(raw.item.name) : null);
|
|
1296
|
+
if (toolName && (type.includes("tool") || type.includes("function"))) {
|
|
1297
|
+
return `Using ${toolName}...`;
|
|
1298
|
+
}
|
|
1299
|
+
const status = readString(raw.status);
|
|
1300
|
+
if (status && !["completed", "done", "finished"].includes(status)) {
|
|
1301
|
+
return truncateText(status);
|
|
1302
|
+
}
|
|
1303
|
+
if (type.includes("turn"))
|
|
1304
|
+
return "Thinking...";
|
|
1305
|
+
return null;
|
|
1306
|
+
}
|
|
1307
|
+
function parseCodexStreamLine(raw, state) {
|
|
1308
|
+
const type = readString(raw.type) ?? readString(raw.event) ?? "unknown";
|
|
1309
|
+
if (type === "response.output_text.delta" && typeof raw.delta === "string") {
|
|
1310
|
+
return [{ kind: "assistant_delta", text: raw.delta }];
|
|
1311
|
+
}
|
|
1312
|
+
if (type === "response.output_text.done") {
|
|
1313
|
+
const text = readString(raw.text) ?? extractTextBlocks(raw.item).trim();
|
|
1314
|
+
return [...text ? streamSnapshot(state, text) : [], ...closeAssistant(state)];
|
|
1315
|
+
}
|
|
1316
|
+
if (type.includes("error")) {
|
|
1317
|
+
const message = isRecord(raw.error) ? readString(raw.error.message) : readString(raw.message);
|
|
1318
|
+
return emitWarning(state, message ?? "Codex returned an error.");
|
|
1319
|
+
}
|
|
1320
|
+
const status = parseCodexStatus(raw, type);
|
|
1321
|
+
if (status)
|
|
1322
|
+
return emitStatus(state, status);
|
|
1323
|
+
if (type.includes("delta") && typeof raw.delta === "string") {
|
|
1324
|
+
return [{ kind: "assistant_delta", text: raw.delta }];
|
|
1325
|
+
}
|
|
1326
|
+
const snapshot = extractStructuredMessage(raw);
|
|
1327
|
+
if (snapshot && (type.includes("assistant") || type.includes("message") || type.includes("output") || type.includes("response"))) {
|
|
1328
|
+
const events = streamSnapshot(state, snapshot);
|
|
1329
|
+
if (type.includes("done") || type.includes("completed") || type.includes("finished")) {
|
|
1330
|
+
events.push(...closeAssistant(state));
|
|
1331
|
+
}
|
|
1332
|
+
return events;
|
|
1333
|
+
}
|
|
1334
|
+
if (type.includes("done") || type.includes("completed") || type.includes("finished")) {
|
|
1335
|
+
return closeAssistant(state);
|
|
1336
|
+
}
|
|
1337
|
+
return [];
|
|
1338
|
+
}
|
|
1339
|
+
function parseInitAgentStreamLine(agent, line, state = { assistantSnapshot: "", lastStatus: null }) {
|
|
1340
|
+
const trimmed = line.trim();
|
|
1341
|
+
if (!trimmed)
|
|
1342
|
+
return [];
|
|
1343
|
+
let parsed;
|
|
1344
|
+
try {
|
|
1345
|
+
parsed = JSON.parse(trimmed);
|
|
1346
|
+
} catch {
|
|
1347
|
+
return [];
|
|
1348
|
+
}
|
|
1349
|
+
if (!isRecord(parsed))
|
|
1350
|
+
return [];
|
|
1351
|
+
return agent === "claude" ? parseClaudeStreamLine(parsed, state) : parseCodexStreamLine(parsed, state);
|
|
1352
|
+
}
|
|
1353
|
+
async function consumeStructuredStream(stream, agent, onEvent) {
|
|
1354
|
+
if (!stream)
|
|
1355
|
+
return { raw: "", assistantText: "" };
|
|
1356
|
+
const reader = stream.getReader();
|
|
1357
|
+
const decoder = new TextDecoder;
|
|
1358
|
+
const state = { assistantSnapshot: "", lastStatus: null };
|
|
1359
|
+
let buffer = "";
|
|
1360
|
+
let raw = "";
|
|
1361
|
+
let assistantText = "";
|
|
1362
|
+
while (true) {
|
|
1363
|
+
const { done, value } = await reader.read();
|
|
1364
|
+
if (done)
|
|
1365
|
+
break;
|
|
1366
|
+
const text = decoder.decode(value, { stream: true });
|
|
1367
|
+
raw += text;
|
|
1368
|
+
buffer += text;
|
|
1369
|
+
let newlineIndex = buffer.indexOf(`
|
|
1370
|
+
`);
|
|
1371
|
+
while (newlineIndex !== -1) {
|
|
1372
|
+
const line = buffer.slice(0, newlineIndex).replace(/\r$/, "");
|
|
1373
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
1374
|
+
for (const event of parseInitAgentStreamLine(agent, line, state)) {
|
|
1375
|
+
if (event.kind === "assistant_delta")
|
|
1376
|
+
assistantText += event.text;
|
|
1377
|
+
onEvent?.(event);
|
|
1378
|
+
}
|
|
1379
|
+
newlineIndex = buffer.indexOf(`
|
|
1380
|
+
`);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
const tail = decoder.decode();
|
|
1384
|
+
raw += tail;
|
|
1385
|
+
buffer += tail;
|
|
1386
|
+
const finalLine = buffer.replace(/\r$/, "");
|
|
1387
|
+
if (finalLine) {
|
|
1388
|
+
for (const event of parseInitAgentStreamLine(agent, finalLine, state)) {
|
|
1389
|
+
if (event.kind === "assistant_delta")
|
|
1390
|
+
assistantText += event.text;
|
|
1391
|
+
onEvent?.(event);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
for (const event of closeAssistant(state)) {
|
|
1395
|
+
onEvent?.(event);
|
|
1396
|
+
}
|
|
1397
|
+
return { raw, assistantText };
|
|
1398
|
+
}
|
|
1399
|
+
async function consumeRawStream(stream) {
|
|
1400
|
+
if (!stream)
|
|
1401
|
+
return "";
|
|
1402
|
+
const reader = stream.getReader();
|
|
1403
|
+
const decoder = new TextDecoder;
|
|
1404
|
+
let raw = "";
|
|
1405
|
+
while (true) {
|
|
1406
|
+
const { done, value } = await reader.read();
|
|
1407
|
+
if (done)
|
|
1408
|
+
break;
|
|
1409
|
+
raw += decoder.decode(value, { stream: true });
|
|
1410
|
+
}
|
|
1411
|
+
return raw + decoder.decode();
|
|
1412
|
+
}
|
|
1413
|
+
async function runInitAgentCommand(spec, cwd, handlers = {}) {
|
|
1414
|
+
const proc = Bun.spawn([spec.cmd, ...spec.args], {
|
|
1415
|
+
cwd,
|
|
1416
|
+
stdout: "pipe",
|
|
1417
|
+
stderr: "pipe"
|
|
1418
|
+
});
|
|
1419
|
+
const [exitCode, stdoutResult, stderr] = await Promise.all([
|
|
1420
|
+
proc.exited,
|
|
1421
|
+
consumeStructuredStream(proc.stdout, spec.agent, handlers.onEvent),
|
|
1422
|
+
consumeRawStream(proc.stderr)
|
|
1423
|
+
]);
|
|
1424
|
+
let summary = stdoutResult.assistantText.trim();
|
|
1425
|
+
if (spec.summaryPath && existsSync2(spec.summaryPath)) {
|
|
1426
|
+
try {
|
|
1427
|
+
summary = readFileSync2(spec.summaryPath, "utf8").trim() || summary;
|
|
1428
|
+
} finally {
|
|
1429
|
+
rmSync(spec.summaryPath, { force: true });
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
return { exitCode, stdout: stdoutResult.raw, stderr, summary };
|
|
1433
|
+
}
|
|
1434
|
+
function buildStarterTemplate(input) {
|
|
1435
|
+
const defaultAgent = input.defaultAgent ?? "claude";
|
|
1436
|
+
const packageManager = input.packageManager ?? "npm";
|
|
1437
|
+
const devCommand = buildRunScriptCommand(packageManager, "dev");
|
|
1438
|
+
return `# Project display name in the dashboard
|
|
1439
|
+
name: ${input.projectName}
|
|
1440
|
+
|
|
1441
|
+
workspace:
|
|
1442
|
+
mainBranch: ${input.mainBranch}
|
|
1443
|
+
worktreeRoot: __worktrees
|
|
1444
|
+
defaultAgent: ${defaultAgent}
|
|
1445
|
+
|
|
1446
|
+
# Each service defines a port env var that webmux injects into pane and agent
|
|
1447
|
+
# process environments when creating a worktree. Ports are auto-assigned:
|
|
1448
|
+
# base + (slot x step).
|
|
1449
|
+
services:
|
|
1450
|
+
- name: app
|
|
1451
|
+
portEnv: PORT
|
|
1452
|
+
portStart: 3000
|
|
1453
|
+
portStep: 10
|
|
1454
|
+
|
|
1455
|
+
profiles:
|
|
1456
|
+
default:
|
|
1457
|
+
runtime: host
|
|
1458
|
+
yolo: false
|
|
1459
|
+
envPassthrough: []
|
|
1460
|
+
panes:
|
|
1461
|
+
- id: agent
|
|
1462
|
+
kind: agent
|
|
1463
|
+
focus: true
|
|
1464
|
+
- id: app
|
|
1465
|
+
kind: command
|
|
1466
|
+
split: right
|
|
1467
|
+
command: PORT=$PORT ${devCommand}
|
|
1468
|
+
|
|
1469
|
+
integrations:
|
|
1470
|
+
github:
|
|
1471
|
+
linkedRepos: []
|
|
1472
|
+
linear:
|
|
1473
|
+
enabled: true
|
|
1474
|
+
|
|
1475
|
+
startupEnvs: {}
|
|
1476
|
+
`;
|
|
1477
|
+
}
|
|
1478
|
+
var FAST_CLAUDE_MODEL = "haiku", FAST_CLAUDE_EFFORT = "low", FAST_CODEX_MODEL = "gpt-5.1-codex", FAST_CODEX_REASONING = "low";
|
|
1479
|
+
var init_init_helpers = __esm(() => {
|
|
1480
|
+
init_shared();
|
|
1481
|
+
});
|
|
1482
|
+
|
|
905
1483
|
// bin/src/init.ts
|
|
906
1484
|
var exports_init = {};
|
|
907
|
-
import { existsSync as
|
|
908
|
-
import { join as
|
|
1485
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1486
|
+
import { join as join3 } from "path";
|
|
909
1487
|
function checkDeps() {
|
|
910
1488
|
const missing = [];
|
|
911
1489
|
for (const dep of deps) {
|
|
@@ -921,82 +1499,77 @@ function checkDeps() {
|
|
|
921
1499
|
}
|
|
922
1500
|
return missing;
|
|
923
1501
|
}
|
|
924
|
-
function
|
|
925
|
-
return
|
|
926
|
-
name: ${name}
|
|
927
|
-
|
|
928
|
-
# Each service defines a port env var that webmux injects into .env.local
|
|
929
|
-
# when creating a worktree. Ports are auto-assigned: base + (slot \xD7 step).
|
|
930
|
-
# Use \`source .env.local\` in your .workmux.yaml pane commands to pick them up.
|
|
931
|
-
services:
|
|
932
|
-
- name: app
|
|
933
|
-
portEnv: PORT
|
|
934
|
-
portStart: 3000 # Port for the main branch (slot 0)
|
|
935
|
-
portStep: 10 # Increment per worktree (3010, 3020, ...)
|
|
936
|
-
|
|
937
|
-
# Agent profiles determine how AI agents run in worktrees
|
|
938
|
-
profiles:
|
|
939
|
-
default:
|
|
940
|
-
name: default
|
|
941
|
-
|
|
942
|
-
# --- Sandbox profile (uncomment to enable) ---
|
|
943
|
-
# Runs agents in Docker containers for full isolation.
|
|
944
|
-
# Requires: docker + a built image.
|
|
945
|
-
# sandbox:
|
|
946
|
-
# name: sandbox
|
|
947
|
-
# image: my-project-sandbox
|
|
948
|
-
# envPassthrough: # Env vars forwarded into the container
|
|
949
|
-
# - DATABASE_URL
|
|
950
|
-
# systemPrompt: >
|
|
951
|
-
# You are running inside a sandboxed container.
|
|
952
|
-
# Start the dev server with: npm run dev
|
|
953
|
-
|
|
954
|
-
# --- Linked repos (uncomment to enable) ---
|
|
955
|
-
# Monitor PRs from related repos in the dashboard.
|
|
956
|
-
# linkedRepos:
|
|
957
|
-
# - repo: org/other-repo
|
|
958
|
-
# alias: other
|
|
959
|
-
|
|
960
|
-
# --- Startup environment variables ---
|
|
961
|
-
# These will appear as configurable fields in the UI when creating a worktree.
|
|
962
|
-
# startupEnvs:
|
|
963
|
-
# NODE_ENV: development
|
|
964
|
-
`;
|
|
1502
|
+
function agentLabel(agent) {
|
|
1503
|
+
return agent === "claude" ? "Claude" : "Codex";
|
|
965
1504
|
}
|
|
966
|
-
function
|
|
967
|
-
return
|
|
968
|
-
|
|
969
|
-
panes:
|
|
970
|
-
# Agent pane \u2014 runs the AI coding assistant
|
|
971
|
-
- command: >-
|
|
972
|
-
claude --append-system-prompt
|
|
973
|
-
"You are running inside a git worktree managed by workmux.\\n
|
|
974
|
-
Pane layout (current window):\\n
|
|
975
|
-
- Pane 0: this pane (claude agent)\\n
|
|
976
|
-
- Pane 1: dev server\\n\\n
|
|
977
|
-
To check dev server logs: tmux capture-pane -t .1 -p -S -50\\n
|
|
978
|
-
To restart dev server: tmux send-keys -t .1 C-c 'source .env.local && PORT=\\$PORT npm run dev' Enter"
|
|
979
|
-
focus: true
|
|
980
|
-
|
|
981
|
-
# Dev server \u2014 waits for .env.local (written by webmux) then starts
|
|
982
|
-
- command: >-
|
|
983
|
-
npm install &&
|
|
984
|
-
until [ -f .env.local ]; do sleep 0.2; done;
|
|
985
|
-
source .env.local;
|
|
986
|
-
PORT=$PORT npm run dev
|
|
987
|
-
split: horizontal
|
|
988
|
-
`;
|
|
1505
|
+
function defaultTemplateAgent() {
|
|
1506
|
+
return which("codex") && !which("claude") ? "codex" : "claude";
|
|
989
1507
|
}
|
|
990
|
-
|
|
1508
|
+
function createAgentStreamPrinter(label) {
|
|
1509
|
+
const prefix = ` ${label}: `;
|
|
1510
|
+
let atLineStart = true;
|
|
1511
|
+
let assistantActive = false;
|
|
1512
|
+
let sawAssistantText = false;
|
|
1513
|
+
const closeAssistantLine = () => {
|
|
1514
|
+
if (assistantActive && !atLineStart) {
|
|
1515
|
+
process.stdout.write(`
|
|
1516
|
+
`);
|
|
1517
|
+
}
|
|
1518
|
+
assistantActive = false;
|
|
1519
|
+
atLineStart = true;
|
|
1520
|
+
};
|
|
1521
|
+
const writeAssistantChunk = (text) => {
|
|
1522
|
+
if (!text)
|
|
1523
|
+
return;
|
|
1524
|
+
assistantActive = true;
|
|
1525
|
+
sawAssistantText = true;
|
|
1526
|
+
for (const char of text) {
|
|
1527
|
+
if (atLineStart) {
|
|
1528
|
+
process.stdout.write(prefix);
|
|
1529
|
+
atLineStart = false;
|
|
1530
|
+
}
|
|
1531
|
+
process.stdout.write(char);
|
|
1532
|
+
if (char === `
|
|
1533
|
+
`) {
|
|
1534
|
+
atLineStart = true;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
return {
|
|
1539
|
+
onEvent(event) {
|
|
1540
|
+
if (event.kind === "assistant_delta") {
|
|
1541
|
+
writeAssistantChunk(event.text);
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
if (event.kind === "assistant_done") {
|
|
1545
|
+
closeAssistantLine();
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
closeAssistantLine();
|
|
1549
|
+
const tag = event.kind === "warning" ? "warning" : "status";
|
|
1550
|
+
console.log(` ${label} ${tag}: ${event.text}`);
|
|
1551
|
+
},
|
|
1552
|
+
finish() {
|
|
1553
|
+
closeAssistantLine();
|
|
1554
|
+
},
|
|
1555
|
+
sawAssistantText() {
|
|
1556
|
+
return sawAssistantText;
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
var deps, gitRoot, missing, webmuxYaml;
|
|
991
1561
|
var init_init = __esm(async () => {
|
|
992
1562
|
init_dist2();
|
|
993
1563
|
init_shared();
|
|
1564
|
+
init_init_helpers();
|
|
994
1565
|
deps = [
|
|
995
1566
|
{ tool: "git", required: true, hint: "https://git-scm.com/downloads" },
|
|
996
1567
|
{ tool: "bun", required: true, hint: "https://bun.sh" },
|
|
1568
|
+
{ tool: "python3", required: true, hint: "https://www.python.org/downloads/ or brew install python or sudo apt install python3" },
|
|
997
1569
|
{ tool: "tmux", required: true, hint: "brew install tmux / sudo apt install tmux" },
|
|
998
|
-
{ tool: "workmux", required: true, hint: "cargo install workmux or https://workmux.raine.dev" },
|
|
999
1570
|
{ tool: "gh", required: false, hint: "brew install gh then gh auth login" },
|
|
1571
|
+
{ tool: "claude", required: false, hint: "Install the Claude Code CLI to let Claude scaffold .webmux.yaml" },
|
|
1572
|
+
{ tool: "codex", required: false, hint: "Install the Codex CLI to let Codex scaffold .webmux.yaml" },
|
|
1000
1573
|
{ tool: "docker", required: false, hint: "https://docs.docker.com/get-started/get-docker/" }
|
|
1001
1574
|
];
|
|
1002
1575
|
Wt2("webmux init");
|
|
@@ -1025,26 +1598,107 @@ var init_init = __esm(async () => {
|
|
|
1025
1598
|
}
|
|
1026
1599
|
}
|
|
1027
1600
|
R2.step("Checking config files...");
|
|
1028
|
-
|
|
1029
|
-
if (
|
|
1030
|
-
R2.info(".workmux.yaml already exists, skipping");
|
|
1031
|
-
} else {
|
|
1032
|
-
await Bun.write(workmuxYaml, workmuxTemplate());
|
|
1033
|
-
R2.success(".workmux.yaml created");
|
|
1034
|
-
}
|
|
1035
|
-
webmuxYaml = join2(gitRoot, ".webmux.yaml");
|
|
1036
|
-
if (existsSync2(webmuxYaml)) {
|
|
1601
|
+
webmuxYaml = join3(gitRoot, ".webmux.yaml");
|
|
1602
|
+
if (existsSync3(webmuxYaml)) {
|
|
1037
1603
|
R2.info(".webmux.yaml already exists, skipping");
|
|
1038
1604
|
} else {
|
|
1039
|
-
const
|
|
1040
|
-
|
|
1041
|
-
|
|
1605
|
+
const claudeAvailable = which("claude");
|
|
1606
|
+
const codexAvailable = which("codex");
|
|
1607
|
+
const choice = await Jt({
|
|
1608
|
+
message: "No .webmux.yaml found. How should webmux create it?",
|
|
1609
|
+
initialValue: claudeAvailable ? "claude" : codexAvailable ? "codex" : "manual",
|
|
1610
|
+
options: [
|
|
1611
|
+
{
|
|
1612
|
+
value: "claude",
|
|
1613
|
+
label: "Claude",
|
|
1614
|
+
hint: claudeAvailable ? "Claude inspects the repo and adapts the starter .webmux.yaml" : "Claude CLI not found",
|
|
1615
|
+
disabled: !claudeAvailable
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
value: "codex",
|
|
1619
|
+
label: "Codex",
|
|
1620
|
+
hint: codexAvailable ? "Codex inspects the repo and adapts the starter .webmux.yaml" : "Codex CLI not found",
|
|
1621
|
+
disabled: !codexAvailable
|
|
1622
|
+
},
|
|
1623
|
+
{
|
|
1624
|
+
value: "manual",
|
|
1625
|
+
label: "I'll do it myself",
|
|
1626
|
+
hint: "Create the starter template now so you can edit it manually"
|
|
1627
|
+
}
|
|
1628
|
+
]
|
|
1629
|
+
});
|
|
1630
|
+
if (Ct(choice)) {
|
|
1631
|
+
Gt("Aborted.");
|
|
1632
|
+
process.exit(1);
|
|
1633
|
+
}
|
|
1634
|
+
const selectedAgent = choice === "codex" ? "codex" : defaultTemplateAgent();
|
|
1635
|
+
const context = detectInitProjectContext(gitRoot, selectedAgent);
|
|
1636
|
+
if (choice === "manual") {
|
|
1637
|
+
await Bun.write(webmuxYaml, buildStarterTemplate({
|
|
1638
|
+
projectName: context.projectName,
|
|
1639
|
+
mainBranch: context.mainBranch,
|
|
1640
|
+
defaultAgent: context.defaultAgent,
|
|
1641
|
+
packageManager: context.packageManager
|
|
1642
|
+
}));
|
|
1643
|
+
R2.success(".webmux.yaml starter template created");
|
|
1644
|
+
} else {
|
|
1645
|
+
const label = agentLabel(choice);
|
|
1646
|
+
const starterTemplate = buildStarterTemplate({
|
|
1647
|
+
projectName: context.projectName,
|
|
1648
|
+
mainBranch: context.mainBranch,
|
|
1649
|
+
defaultAgent: choice,
|
|
1650
|
+
packageManager: context.packageManager
|
|
1651
|
+
});
|
|
1652
|
+
await Bun.write(webmuxYaml, starterTemplate);
|
|
1653
|
+
const prompt = buildInitPromptSpec({ ...context, defaultAgent: choice });
|
|
1654
|
+
const command = buildInitAgentCommand(choice, prompt);
|
|
1655
|
+
const streamPrinter = createAgentStreamPrinter(label);
|
|
1656
|
+
R2.step(`Running ${label} to adapt the starter .webmux.yaml...`);
|
|
1657
|
+
const result = await runInitAgentCommand(command, gitRoot, { onEvent: streamPrinter.onEvent });
|
|
1658
|
+
streamPrinter.finish();
|
|
1659
|
+
if (!existsSync3(webmuxYaml)) {
|
|
1660
|
+
R2.error(`${label} removed .webmux.yaml`);
|
|
1661
|
+
const details = [
|
|
1662
|
+
result.summary ? `Summary:
|
|
1663
|
+
${result.summary}` : "",
|
|
1664
|
+
result.stderr.trim() ? `stderr:
|
|
1665
|
+
${result.stderr.trim()}` : ""
|
|
1666
|
+
].filter((entry) => entry.length > 0).join(`
|
|
1667
|
+
|
|
1668
|
+
`);
|
|
1669
|
+
if (details) {
|
|
1670
|
+
Vt2(details, `${label} output`);
|
|
1671
|
+
}
|
|
1672
|
+
Gt("Setup incomplete.");
|
|
1673
|
+
process.exit(1);
|
|
1674
|
+
}
|
|
1675
|
+
const finalYaml = await Bun.file(webmuxYaml).text();
|
|
1676
|
+
const changedTemplate = finalYaml !== starterTemplate;
|
|
1677
|
+
if (result.exitCode === 0 && changedTemplate) {
|
|
1678
|
+
R2.success(`${label} adapted .webmux.yaml`);
|
|
1679
|
+
} else if (result.exitCode === 0) {
|
|
1680
|
+
R2.warning(`${label} left the starter template unchanged`);
|
|
1681
|
+
R2.warning(`${label} did not change the starter template. Review .webmux.yaml manually.`);
|
|
1682
|
+
} else if (changedTemplate) {
|
|
1683
|
+
R2.warning(`${label} updated .webmux.yaml`);
|
|
1684
|
+
R2.warning(`${label} exited with code ${result.exitCode}. Review the generated file before using it.`);
|
|
1685
|
+
} else {
|
|
1686
|
+
R2.warning(`${label} left the starter template in place`);
|
|
1687
|
+
R2.warning(`${label} exited with code ${result.exitCode}. The starter template is still there for manual editing.`);
|
|
1688
|
+
}
|
|
1689
|
+
if (result.summary && !streamPrinter.sawAssistantText()) {
|
|
1690
|
+
Vt2(result.summary, `${label} summary`);
|
|
1691
|
+
}
|
|
1692
|
+
const trimmedStderr = result.stderr.trim();
|
|
1693
|
+
if (trimmedStderr) {
|
|
1694
|
+
Vt2(trimmedStderr, `${label} stderr`);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1042
1697
|
}
|
|
1043
1698
|
Gt("You're all set! Next steps:");
|
|
1044
1699
|
console.log();
|
|
1045
|
-
console.log(" 1.
|
|
1046
|
-
console.log(" 2.
|
|
1047
|
-
console.log(" 3. Run: webmux");
|
|
1700
|
+
console.log(" 1. Review .webmux.yaml and adjust panes, ports, and profiles if needed");
|
|
1701
|
+
console.log(" 2. Run: webmux");
|
|
1048
1702
|
console.log();
|
|
1049
1703
|
});
|
|
1050
1704
|
|
|
@@ -1053,8 +1707,8 @@ var exports_service = {};
|
|
|
1053
1707
|
__export(exports_service, {
|
|
1054
1708
|
default: () => service
|
|
1055
1709
|
});
|
|
1056
|
-
import { existsSync as
|
|
1057
|
-
import { join as
|
|
1710
|
+
import { existsSync as existsSync4, mkdirSync, unlinkSync } from "fs";
|
|
1711
|
+
import { join as join4 } from "path";
|
|
1058
1712
|
import { homedir } from "os";
|
|
1059
1713
|
function getPlatform() {
|
|
1060
1714
|
const plat = process.platform;
|
|
@@ -1084,10 +1738,10 @@ function printRunResult(result) {
|
|
|
1084
1738
|
console.error(err);
|
|
1085
1739
|
}
|
|
1086
1740
|
function systemdUnitPath(serviceName) {
|
|
1087
|
-
return
|
|
1741
|
+
return join4(homedir(), ".config", "systemd", "user", `${serviceName}.service`);
|
|
1088
1742
|
}
|
|
1089
1743
|
function launchdPlistPath(serviceName) {
|
|
1090
|
-
return
|
|
1744
|
+
return join4(homedir(), "Library", "LaunchAgents", `com.webmux.${serviceName}.plist`);
|
|
1091
1745
|
}
|
|
1092
1746
|
function serviceFilePath(config) {
|
|
1093
1747
|
if (config.platform === "linux")
|
|
@@ -1113,7 +1767,7 @@ WantedBy=default.target
|
|
|
1113
1767
|
`;
|
|
1114
1768
|
}
|
|
1115
1769
|
function generateLaunchdPlist(config) {
|
|
1116
|
-
const logPath =
|
|
1770
|
+
const logPath = join4(homedir(), "Library", "Logs", `webmux-${config.serviceName}.log`);
|
|
1117
1771
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1118
1772
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1119
1773
|
<plist version="1.0">
|
|
@@ -1180,7 +1834,7 @@ function uninstallCommands(config) {
|
|
|
1180
1834
|
];
|
|
1181
1835
|
}
|
|
1182
1836
|
function isInstalled(config) {
|
|
1183
|
-
return
|
|
1837
|
+
return existsSync4(serviceFilePath(config));
|
|
1184
1838
|
}
|
|
1185
1839
|
async function install(config) {
|
|
1186
1840
|
const filePath = serviceFilePath(config);
|
|
@@ -1284,8 +1938,8 @@ function logs(config) {
|
|
|
1284
1938
|
if (config.platform === "linux") {
|
|
1285
1939
|
proc = Bun.spawn(["journalctl", "--user", "-u", config.serviceName, "-f", "--no-pager"], { stdout: "inherit", stderr: "inherit" });
|
|
1286
1940
|
} else {
|
|
1287
|
-
const logPath =
|
|
1288
|
-
if (!
|
|
1941
|
+
const logPath = join4(homedir(), "Library", "Logs", `webmux-${config.serviceName}.log`);
|
|
1942
|
+
if (!existsSync4(logPath)) {
|
|
1289
1943
|
R2.error(`Log file not found: ${logPath}`);
|
|
1290
1944
|
return;
|
|
1291
1945
|
}
|
|
@@ -1382,8 +2036,8 @@ var init_service = __esm(() => {
|
|
|
1382
2036
|
});
|
|
1383
2037
|
|
|
1384
2038
|
// bin/src/webmux.ts
|
|
1385
|
-
import { resolve, dirname, join as
|
|
1386
|
-
import { existsSync as
|
|
2039
|
+
import { resolve, dirname, join as join5 } from "path";
|
|
2040
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1387
2041
|
import { fileURLToPath } from "url";
|
|
1388
2042
|
var PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
1389
2043
|
function usage2() {
|
|
@@ -1437,12 +2091,12 @@ Run webmux --help for usage.`);
|
|
|
1437
2091
|
process.exit(1);
|
|
1438
2092
|
}
|
|
1439
2093
|
}
|
|
1440
|
-
if (!
|
|
2094
|
+
if (!existsSync5(resolve(process.cwd(), ".webmux.yaml"))) {
|
|
1441
2095
|
console.error("No .webmux.yaml found in this directory.\nRun `webmux init` to set up your project.");
|
|
1442
2096
|
process.exit(1);
|
|
1443
2097
|
}
|
|
1444
2098
|
async function loadEnvFile(path) {
|
|
1445
|
-
if (!
|
|
2099
|
+
if (!existsSync5(path))
|
|
1446
2100
|
return;
|
|
1447
2101
|
const lines = (await Bun.file(path).text()).split(`
|
|
1448
2102
|
`);
|
|
@@ -1507,9 +2161,9 @@ function cleanup() {
|
|
|
1507
2161
|
}
|
|
1508
2162
|
process.on("SIGINT", cleanup);
|
|
1509
2163
|
process.on("SIGTERM", cleanup);
|
|
1510
|
-
var backendEntry =
|
|
1511
|
-
var staticDir =
|
|
1512
|
-
if (!
|
|
2164
|
+
var backendEntry = join5(PKG_ROOT, "backend", "dist", "server.js");
|
|
2165
|
+
var staticDir = join5(PKG_ROOT, "frontend", "dist");
|
|
2166
|
+
if (!existsSync5(staticDir)) {
|
|
1513
2167
|
console.error(`Error: frontend/dist/ not found. Run 'bun run build' first.`);
|
|
1514
2168
|
process.exit(1);
|
|
1515
2169
|
}
|