@mstar-harness/cli 1.1.0 → 1.2.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/dist/mstar-harness.js +63 -341
- package/package.json +1 -1
package/dist/mstar-harness.js
CHANGED
|
@@ -2351,7 +2351,6 @@ import { fileURLToPath } from "url";
|
|
|
2351
2351
|
// ../../node_modules/@inquirer/core/dist/lib/key.js
|
|
2352
2352
|
var isUpKey = (key, keybindings = []) => key.name === "up" || keybindings.includes("vim") && key.name === "k" || keybindings.includes("emacs") && key.ctrl && key.name === "p";
|
|
2353
2353
|
var isDownKey = (key, keybindings = []) => key.name === "down" || keybindings.includes("vim") && key.name === "j" || keybindings.includes("emacs") && key.ctrl && key.name === "n";
|
|
2354
|
-
var isSpaceKey = (key) => key.name === "space";
|
|
2355
2354
|
var isBackspaceKey = (key) => key.name === "backspace";
|
|
2356
2355
|
var isNumberKey = (key) => "1234567890".includes(key.name);
|
|
2357
2356
|
var isEnterKey = (key) => key.name === "enter" || key.name === "return";
|
|
@@ -3783,23 +3782,17 @@ class Separator {
|
|
|
3783
3782
|
return Boolean(choice && typeof choice === "object" && "type" in choice && choice.type === "separator");
|
|
3784
3783
|
}
|
|
3785
3784
|
}
|
|
3786
|
-
// ../../node_modules/@inquirer/
|
|
3785
|
+
// ../../node_modules/@inquirer/select/dist/index.js
|
|
3787
3786
|
import { styleText as styleText3 } from "node:util";
|
|
3788
|
-
var
|
|
3789
|
-
icon: {
|
|
3790
|
-
checked: styleText3("green", dist_default.circleFilled),
|
|
3791
|
-
unchecked: dist_default.circle,
|
|
3792
|
-
cursor: dist_default.pointer,
|
|
3793
|
-
disabledChecked: styleText3("green", dist_default.circleDouble),
|
|
3794
|
-
disabledUnchecked: "-"
|
|
3795
|
-
},
|
|
3787
|
+
var selectTheme = {
|
|
3788
|
+
icon: { cursor: dist_default.pointer },
|
|
3796
3789
|
style: {
|
|
3797
3790
|
disabled: (text) => styleText3("dim", text),
|
|
3798
|
-
renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(", "),
|
|
3799
3791
|
description: (text) => styleText3("cyan", text),
|
|
3800
3792
|
keysHelpTip: (keys) => keys.map(([key, action]) => `${styleText3("bold", key)} ${styleText3("dim", action)}`).join(styleText3("dim", " • "))
|
|
3801
3793
|
},
|
|
3802
|
-
i18n: { disabledError: "This option is disabled and cannot be
|
|
3794
|
+
i18n: { disabledError: "This option is disabled and cannot be selected." },
|
|
3795
|
+
indexMode: "hidden",
|
|
3803
3796
|
keybindings: []
|
|
3804
3797
|
};
|
|
3805
3798
|
function isSelectable(item) {
|
|
@@ -3808,192 +3801,7 @@ function isSelectable(item) {
|
|
|
3808
3801
|
function isNavigable(item) {
|
|
3809
3802
|
return !Separator.isSeparator(item);
|
|
3810
3803
|
}
|
|
3811
|
-
function isChecked(item) {
|
|
3812
|
-
return !Separator.isSeparator(item) && item.checked;
|
|
3813
|
-
}
|
|
3814
|
-
function toggle(item) {
|
|
3815
|
-
return isSelectable(item) ? { ...item, checked: !item.checked } : item;
|
|
3816
|
-
}
|
|
3817
|
-
function check(checked) {
|
|
3818
|
-
return function(item) {
|
|
3819
|
-
return isSelectable(item) ? { ...item, checked } : item;
|
|
3820
|
-
};
|
|
3821
|
-
}
|
|
3822
3804
|
function normalizeChoices(choices) {
|
|
3823
|
-
return choices.map((choice) => {
|
|
3824
|
-
if (Separator.isSeparator(choice))
|
|
3825
|
-
return choice;
|
|
3826
|
-
if (typeof choice !== "object" || choice === null || !("value" in choice)) {
|
|
3827
|
-
const name2 = String(choice);
|
|
3828
|
-
return {
|
|
3829
|
-
value: choice,
|
|
3830
|
-
name: name2,
|
|
3831
|
-
short: name2,
|
|
3832
|
-
checkedName: name2,
|
|
3833
|
-
disabled: false,
|
|
3834
|
-
checked: false
|
|
3835
|
-
};
|
|
3836
|
-
}
|
|
3837
|
-
const name = choice.name ?? String(choice.value);
|
|
3838
|
-
const normalizedChoice = {
|
|
3839
|
-
value: choice.value,
|
|
3840
|
-
name,
|
|
3841
|
-
short: choice.short ?? name,
|
|
3842
|
-
checkedName: choice.checkedName ?? name,
|
|
3843
|
-
disabled: choice.disabled ?? false,
|
|
3844
|
-
checked: choice.checked ?? false
|
|
3845
|
-
};
|
|
3846
|
-
if (choice.description) {
|
|
3847
|
-
normalizedChoice.description = choice.description;
|
|
3848
|
-
}
|
|
3849
|
-
return normalizedChoice;
|
|
3850
|
-
});
|
|
3851
|
-
}
|
|
3852
|
-
var dist_default4 = createPrompt((config, done) => {
|
|
3853
|
-
const { pageSize = 7, loop = true, required, validate = () => true } = config;
|
|
3854
|
-
const shortcuts = { all: "a", invert: "i", ...config.shortcuts };
|
|
3855
|
-
const theme = makeTheme(checkboxTheme, config.theme);
|
|
3856
|
-
const { keybindings } = theme;
|
|
3857
|
-
const [status, setStatus] = useState("idle");
|
|
3858
|
-
const prefix = usePrefix({ status, theme });
|
|
3859
|
-
const [items, setItems] = useState(normalizeChoices(config.choices));
|
|
3860
|
-
const bounds = useMemo(() => {
|
|
3861
|
-
const first = items.findIndex(isNavigable);
|
|
3862
|
-
const last = items.findLastIndex(isNavigable);
|
|
3863
|
-
if (first === -1) {
|
|
3864
|
-
throw new ValidationError("[checkbox prompt] No selectable choices. All choices are disabled.");
|
|
3865
|
-
}
|
|
3866
|
-
return { first, last };
|
|
3867
|
-
}, [items]);
|
|
3868
|
-
const [active, setActive] = useState(bounds.first);
|
|
3869
|
-
const [errorMsg, setError] = useState();
|
|
3870
|
-
useKeypress(async (key) => {
|
|
3871
|
-
if (isEnterKey(key)) {
|
|
3872
|
-
const selection = items.filter(isChecked);
|
|
3873
|
-
const isValid = await validate([...selection]);
|
|
3874
|
-
if (required && !selection.length) {
|
|
3875
|
-
setError("At least one choice must be selected");
|
|
3876
|
-
} else if (isValid === true) {
|
|
3877
|
-
setStatus("done");
|
|
3878
|
-
done(selection.map((choice) => choice.value));
|
|
3879
|
-
} else {
|
|
3880
|
-
setError(isValid || "You must select a valid value");
|
|
3881
|
-
}
|
|
3882
|
-
} else if (isUpKey(key, keybindings) || isDownKey(key, keybindings)) {
|
|
3883
|
-
if (errorMsg) {
|
|
3884
|
-
setError(undefined);
|
|
3885
|
-
}
|
|
3886
|
-
if (loop || isUpKey(key, keybindings) && active !== bounds.first || isDownKey(key, keybindings) && active !== bounds.last) {
|
|
3887
|
-
const offset = isUpKey(key, keybindings) ? -1 : 1;
|
|
3888
|
-
let next = active;
|
|
3889
|
-
do {
|
|
3890
|
-
next = (next + offset + items.length) % items.length;
|
|
3891
|
-
} while (!isNavigable(items[next]));
|
|
3892
|
-
setActive(next);
|
|
3893
|
-
}
|
|
3894
|
-
} else if (isSpaceKey(key)) {
|
|
3895
|
-
const activeItem = items[active];
|
|
3896
|
-
if (activeItem && !Separator.isSeparator(activeItem)) {
|
|
3897
|
-
if (activeItem.disabled) {
|
|
3898
|
-
setError(theme.i18n.disabledError);
|
|
3899
|
-
} else {
|
|
3900
|
-
setError(undefined);
|
|
3901
|
-
setItems(items.map((choice, i) => i === active ? toggle(choice) : choice));
|
|
3902
|
-
}
|
|
3903
|
-
}
|
|
3904
|
-
} else if (key.name === shortcuts.all) {
|
|
3905
|
-
const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked);
|
|
3906
|
-
setItems(items.map(check(selectAll)));
|
|
3907
|
-
} else if (key.name === shortcuts.invert) {
|
|
3908
|
-
setItems(items.map(toggle));
|
|
3909
|
-
} else if (isNumberKey(key)) {
|
|
3910
|
-
const selectedIndex = Number(key.name) - 1;
|
|
3911
|
-
let selectableIndex = -1;
|
|
3912
|
-
const position = items.findIndex((item) => {
|
|
3913
|
-
if (Separator.isSeparator(item))
|
|
3914
|
-
return false;
|
|
3915
|
-
selectableIndex++;
|
|
3916
|
-
return selectableIndex === selectedIndex;
|
|
3917
|
-
});
|
|
3918
|
-
const selectedItem = items[position];
|
|
3919
|
-
if (selectedItem && isSelectable(selectedItem)) {
|
|
3920
|
-
setActive(position);
|
|
3921
|
-
setItems(items.map((choice, i) => i === position ? toggle(choice) : choice));
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
});
|
|
3925
|
-
const message = theme.style.message(config.message, status);
|
|
3926
|
-
let description;
|
|
3927
|
-
const page = usePagination({
|
|
3928
|
-
items,
|
|
3929
|
-
active,
|
|
3930
|
-
renderItem({ item, isActive }) {
|
|
3931
|
-
if (Separator.isSeparator(item)) {
|
|
3932
|
-
return ` ${item.separator}`;
|
|
3933
|
-
}
|
|
3934
|
-
const cursor = isActive ? theme.icon.cursor : " ";
|
|
3935
|
-
if (item.disabled) {
|
|
3936
|
-
const disabledLabel = typeof item.disabled === "string" ? item.disabled : "(disabled)";
|
|
3937
|
-
const checkbox2 = item.checked ? theme.icon.disabledChecked : theme.icon.disabledUnchecked;
|
|
3938
|
-
return theme.style.disabled(`${cursor}${checkbox2} ${item.name} ${disabledLabel}`);
|
|
3939
|
-
}
|
|
3940
|
-
if (isActive) {
|
|
3941
|
-
description = item.description;
|
|
3942
|
-
}
|
|
3943
|
-
const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
|
|
3944
|
-
const name = item.checked ? item.checkedName : item.name;
|
|
3945
|
-
const color = isActive ? theme.style.highlight : (x) => x;
|
|
3946
|
-
return color(`${cursor}${checkbox} ${name}`);
|
|
3947
|
-
},
|
|
3948
|
-
pageSize,
|
|
3949
|
-
loop
|
|
3950
|
-
});
|
|
3951
|
-
if (status === "done") {
|
|
3952
|
-
const selection = items.filter(isChecked);
|
|
3953
|
-
const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
|
|
3954
|
-
return [prefix, message, answer].filter(Boolean).join(" ");
|
|
3955
|
-
}
|
|
3956
|
-
const keys = [
|
|
3957
|
-
["↑↓", "navigate"],
|
|
3958
|
-
["space", "select"]
|
|
3959
|
-
];
|
|
3960
|
-
if (shortcuts.all)
|
|
3961
|
-
keys.push([shortcuts.all, "all"]);
|
|
3962
|
-
if (shortcuts.invert)
|
|
3963
|
-
keys.push([shortcuts.invert, "invert"]);
|
|
3964
|
-
keys.push(["⏎", "submit"]);
|
|
3965
|
-
const helpLine = theme.style.keysHelpTip(keys);
|
|
3966
|
-
const lines = [
|
|
3967
|
-
[prefix, message].filter(Boolean).join(" "),
|
|
3968
|
-
page,
|
|
3969
|
-
" ",
|
|
3970
|
-
description ? theme.style.description(description) : "",
|
|
3971
|
-
errorMsg ? theme.style.error(errorMsg) : "",
|
|
3972
|
-
helpLine
|
|
3973
|
-
].filter(Boolean).join(`
|
|
3974
|
-
`).trimEnd();
|
|
3975
|
-
return `${lines}${cursorHide}`;
|
|
3976
|
-
});
|
|
3977
|
-
// ../../node_modules/@inquirer/select/dist/index.js
|
|
3978
|
-
import { styleText as styleText4 } from "node:util";
|
|
3979
|
-
var selectTheme = {
|
|
3980
|
-
icon: { cursor: dist_default.pointer },
|
|
3981
|
-
style: {
|
|
3982
|
-
disabled: (text) => styleText4("dim", text),
|
|
3983
|
-
description: (text) => styleText4("cyan", text),
|
|
3984
|
-
keysHelpTip: (keys) => keys.map(([key, action]) => `${styleText4("bold", key)} ${styleText4("dim", action)}`).join(styleText4("dim", " • "))
|
|
3985
|
-
},
|
|
3986
|
-
i18n: { disabledError: "This option is disabled and cannot be selected." },
|
|
3987
|
-
indexMode: "hidden",
|
|
3988
|
-
keybindings: []
|
|
3989
|
-
};
|
|
3990
|
-
function isSelectable2(item) {
|
|
3991
|
-
return !Separator.isSeparator(item) && !item.disabled;
|
|
3992
|
-
}
|
|
3993
|
-
function isNavigable2(item) {
|
|
3994
|
-
return !Separator.isSeparator(item);
|
|
3995
|
-
}
|
|
3996
|
-
function normalizeChoices2(choices) {
|
|
3997
3805
|
return choices.map((choice) => {
|
|
3998
3806
|
if (Separator.isSeparator(choice))
|
|
3999
3807
|
return choice;
|
|
@@ -4019,7 +3827,7 @@ function normalizeChoices2(choices) {
|
|
|
4019
3827
|
return normalizedChoice;
|
|
4020
3828
|
});
|
|
4021
3829
|
}
|
|
4022
|
-
var
|
|
3830
|
+
var dist_default4 = createPrompt((config, done) => {
|
|
4023
3831
|
const { loop = true, pageSize = 7 } = config;
|
|
4024
3832
|
const theme = makeTheme(selectTheme, config.theme);
|
|
4025
3833
|
const { keybindings } = theme;
|
|
@@ -4027,10 +3835,10 @@ var dist_default5 = createPrompt((config, done) => {
|
|
|
4027
3835
|
const prefix = usePrefix({ status, theme });
|
|
4028
3836
|
const searchTimeoutRef = useRef();
|
|
4029
3837
|
const searchEnabled = !keybindings.includes("vim");
|
|
4030
|
-
const items = useMemo(() =>
|
|
3838
|
+
const items = useMemo(() => normalizeChoices(config.choices), [config.choices]);
|
|
4031
3839
|
const bounds = useMemo(() => {
|
|
4032
|
-
const first = items.findIndex(
|
|
4033
|
-
const last = items.findLastIndex(
|
|
3840
|
+
const first = items.findIndex(isNavigable);
|
|
3841
|
+
const last = items.findLastIndex(isNavigable);
|
|
4034
3842
|
if (first === -1) {
|
|
4035
3843
|
throw new ValidationError("[select prompt] No selectable choices. All choices are disabled.");
|
|
4036
3844
|
}
|
|
@@ -4039,7 +3847,7 @@ var dist_default5 = createPrompt((config, done) => {
|
|
|
4039
3847
|
const defaultItemIndex = useMemo(() => {
|
|
4040
3848
|
if (!("default" in config))
|
|
4041
3849
|
return -1;
|
|
4042
|
-
return items.findIndex((item) =>
|
|
3850
|
+
return items.findIndex((item) => isSelectable(item) && item.value === config.default);
|
|
4043
3851
|
}, [config.default, items]);
|
|
4044
3852
|
const [active, setActive] = useState(defaultItemIndex === -1 ? bounds.first : defaultItemIndex);
|
|
4045
3853
|
const selectedChoice = items[active];
|
|
@@ -4066,7 +3874,7 @@ var dist_default5 = createPrompt((config, done) => {
|
|
|
4066
3874
|
let next = active;
|
|
4067
3875
|
do {
|
|
4068
3876
|
next = (next + offset + items.length) % items.length;
|
|
4069
|
-
} while (!
|
|
3877
|
+
} while (!isNavigable(items[next]));
|
|
4070
3878
|
setActive(next);
|
|
4071
3879
|
}
|
|
4072
3880
|
} else if (isNumberKey(key) && !Number.isNaN(Number(rl.line))) {
|
|
@@ -4079,7 +3887,7 @@ var dist_default5 = createPrompt((config, done) => {
|
|
|
4079
3887
|
return selectableIndex === selectedIndex;
|
|
4080
3888
|
});
|
|
4081
3889
|
const item = items[position];
|
|
4082
|
-
if (item != null &&
|
|
3890
|
+
if (item != null && isSelectable(item)) {
|
|
4083
3891
|
setActive(position);
|
|
4084
3892
|
}
|
|
4085
3893
|
searchTimeoutRef.current = setTimeout(() => {
|
|
@@ -4090,7 +3898,7 @@ var dist_default5 = createPrompt((config, done) => {
|
|
|
4090
3898
|
} else if (searchEnabled) {
|
|
4091
3899
|
const searchTerm = rl.line.toLowerCase();
|
|
4092
3900
|
const matchIndex = items.findIndex((item) => {
|
|
4093
|
-
if (Separator.isSeparator(item) || !
|
|
3901
|
+
if (Separator.isSeparator(item) || !isSelectable(item))
|
|
4094
3902
|
return false;
|
|
4095
3903
|
return item.name.toLowerCase().startsWith(searchTerm);
|
|
4096
3904
|
});
|
|
@@ -4189,31 +3997,6 @@ var ROLE_GROUPS = {
|
|
|
4189
3997
|
};
|
|
4190
3998
|
|
|
4191
3999
|
// src/assignment.ts
|
|
4192
|
-
function parseSelection(rawValues, models, maxCount, singleOnly = false) {
|
|
4193
|
-
const picked = (rawValues || []).map((item) => item.trim()).filter(Boolean);
|
|
4194
|
-
if (!picked.length)
|
|
4195
|
-
return { ok: false, reason: "empty" };
|
|
4196
|
-
if (singleOnly && picked.length !== 1)
|
|
4197
|
-
return { ok: false, reason: "single_required" };
|
|
4198
|
-
if (picked.length > maxCount)
|
|
4199
|
-
return { ok: false, reason: "too_many" };
|
|
4200
|
-
const invalid = picked.filter((id) => !models.includes(id));
|
|
4201
|
-
if (invalid.length)
|
|
4202
|
-
return { ok: false, reason: "invalid", invalid };
|
|
4203
|
-
return { ok: true, value: picked };
|
|
4204
|
-
}
|
|
4205
|
-
function requireValidatedSelection(label, rawValues, models, maxCount, singleOnly = false) {
|
|
4206
|
-
const parsed = parseSelection(rawValues, models, maxCount, singleOnly);
|
|
4207
|
-
if (parsed.ok)
|
|
4208
|
-
return parsed.value;
|
|
4209
|
-
if (parsed.reason === "empty")
|
|
4210
|
-
throw new Error(`Missing option for ${label}.`);
|
|
4211
|
-
if (parsed.reason === "single_required")
|
|
4212
|
-
throw new Error(`${label} requires exactly one model.`);
|
|
4213
|
-
if (parsed.reason === "too_many")
|
|
4214
|
-
throw new Error(`${label} allows at most ${maxCount} model(s).`);
|
|
4215
|
-
throw new Error(`${label} contains invalid model(s): ${parsed.invalid.join(", ")}`);
|
|
4216
|
-
}
|
|
4217
4000
|
function pickByOrder(roleIds, selectedModels) {
|
|
4218
4001
|
const assignments = {};
|
|
4219
4002
|
for (let i = 0;i < roleIds.length; i += 1) {
|
|
@@ -4248,10 +4031,6 @@ import path4 from "node:path";
|
|
|
4248
4031
|
// src/utils.ts
|
|
4249
4032
|
import fs from "node:fs";
|
|
4250
4033
|
import path2 from "node:path";
|
|
4251
|
-
function normalizeModelList(raw) {
|
|
4252
|
-
return raw.split(`
|
|
4253
|
-
`).map((line) => line.trim()).filter(Boolean);
|
|
4254
|
-
}
|
|
4255
4034
|
function ensureObject(value) {
|
|
4256
4035
|
if (value && typeof value === "object" && !Array.isArray(value))
|
|
4257
4036
|
return value;
|
|
@@ -4732,7 +4511,6 @@ var cursorAdapter = {
|
|
|
4732
4511
|
// src/adapters/opencode.ts
|
|
4733
4512
|
import os4 from "node:os";
|
|
4734
4513
|
import path6 from "node:path";
|
|
4735
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4736
4514
|
var OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
|
|
4737
4515
|
var MSTAR_OPENCODE_PLUGIN = "@mstar-harness/opencode@latest";
|
|
4738
4516
|
function isLegacyMorningStarGitPlugin(plugin) {
|
|
@@ -4750,13 +4528,6 @@ function isMstarHarnessOpencodePlugin(plugin) {
|
|
|
4750
4528
|
function isAnyMstarHarnessOpencodeSlot(plugin) {
|
|
4751
4529
|
return isLegacyMorningStarGitPlugin(plugin) || isMstarHarnessOpencodePlugin(plugin);
|
|
4752
4530
|
}
|
|
4753
|
-
function getOpencodeModels() {
|
|
4754
|
-
const raw = execFileSync2("opencode", ["models"], { encoding: "utf8" });
|
|
4755
|
-
const models = normalizeModelList(raw);
|
|
4756
|
-
if (!models.length)
|
|
4757
|
-
throw new Error("`opencode models` returned no model entries.");
|
|
4758
|
-
return models;
|
|
4759
|
-
}
|
|
4760
4531
|
function resolveOpencodeConfigPath(scope, outputPath) {
|
|
4761
4532
|
if (outputPath && outputPath.trim()) {
|
|
4762
4533
|
const raw = outputPath.trim();
|
|
@@ -4792,6 +4563,8 @@ function updatePluginList(config) {
|
|
|
4792
4563
|
return next;
|
|
4793
4564
|
}
|
|
4794
4565
|
function applyAssignments(config, assignments) {
|
|
4566
|
+
if (!Object.keys(assignments).length)
|
|
4567
|
+
return config;
|
|
4795
4568
|
const next = ensureObject(config);
|
|
4796
4569
|
const agent = ensureObject(next.agent);
|
|
4797
4570
|
next.agent = agent;
|
|
@@ -4809,38 +4582,36 @@ function validateSetup(config) {
|
|
|
4809
4582
|
}
|
|
4810
4583
|
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
4811
4584
|
const hasMstarOpencode = plugins.some((item) => typeof item === "string" && isAnyMstarHarnessOpencodeSlot(item.trim()));
|
|
4812
|
-
if (!hasMstarOpencode)
|
|
4585
|
+
if (!hasMstarOpencode) {
|
|
4813
4586
|
errors2.push("Missing @mstar-harness/opencode plugin entry in `plugin` (or legacy morning-star git plugin).");
|
|
4814
|
-
const agent = ensureObject(config.agent);
|
|
4815
|
-
for (const roleId of ALL_ROLES) {
|
|
4816
|
-
const role = ensureObject(agent[roleId]);
|
|
4817
|
-
if (typeof role.model !== "string" || !role.model.trim()) {
|
|
4818
|
-
errors2.push(`Missing model for role: ${roleId}`);
|
|
4819
|
-
}
|
|
4820
4587
|
}
|
|
4821
4588
|
return errors2;
|
|
4822
4589
|
}
|
|
4823
4590
|
function getDoctorWarnings(config) {
|
|
4591
|
+
const warnings = [];
|
|
4824
4592
|
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
4825
4593
|
const strings = plugins.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
4826
4594
|
const hasNpm = strings.some(isMstarHarnessOpencodePlugin);
|
|
4827
4595
|
const hasLegacy = strings.some(isLegacyMorningStarGitPlugin);
|
|
4828
4596
|
if (hasLegacy && !hasNpm) {
|
|
4829
|
-
|
|
4830
|
-
"Plugin list uses legacy `morning-star@git+…` for this harness; run `mstar-harness init --target opencode` (or `--yes` with model flags) to rewrite to `@mstar-harness/opencode@latest`."
|
|
4831
|
-
];
|
|
4597
|
+
warnings.push("Plugin list uses legacy `morning-star@git+…` for this harness; run `mstar-harness init --target opencode` to rewrite to `@mstar-harness/opencode@latest`.");
|
|
4832
4598
|
}
|
|
4833
4599
|
if (hasLegacy && hasNpm) {
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4600
|
+
warnings.push("Both legacy `morning-star@git+…` and `@mstar-harness/opencode` appear in `plugin`; run `init` again to dedupe and keep a single npm plugin line.");
|
|
4601
|
+
}
|
|
4602
|
+
const agent = ensureObject(config.agent);
|
|
4603
|
+
const missingModels = ALL_ROLES.filter((roleId) => {
|
|
4604
|
+
const role = ensureObject(agent[roleId]);
|
|
4605
|
+
return typeof role.model !== "string" || !role.model.trim();
|
|
4606
|
+
});
|
|
4607
|
+
if (missingModels.length) {
|
|
4608
|
+
warnings.push(`${missingModels.length} role(s) have no explicit agent.<role>.model — OpenCode default model will be used (recommended for fastest setup).`);
|
|
4837
4609
|
}
|
|
4838
|
-
return
|
|
4610
|
+
return warnings;
|
|
4839
4611
|
}
|
|
4840
4612
|
var opencodeAdapter = {
|
|
4841
4613
|
target: "opencode",
|
|
4842
4614
|
mode: "config",
|
|
4843
|
-
getAvailableModels: () => getOpencodeModels(),
|
|
4844
4615
|
resolveConfigPath: (scope, outputPath) => resolveOpencodeConfigPath(scope, outputPath),
|
|
4845
4616
|
mutateConfigForInit: (config, assignments) => {
|
|
4846
4617
|
const withSchema = ensureConfigSchema(config);
|
|
@@ -4852,6 +4623,7 @@ var opencodeAdapter = {
|
|
|
4852
4623
|
printPostSetupSummary: () => {
|
|
4853
4624
|
console.log(`Schema: ${OPENCODE_CONFIG_SCHEMA} (ensured)`);
|
|
4854
4625
|
console.log(`Plugin: ${MSTAR_OPENCODE_PLUGIN} (ensured; legacy git morning-star entries removed)`);
|
|
4626
|
+
console.log("Role models: left to OpenCode defaults (set agent.<role>.model in opencode.json only if you want overrides)");
|
|
4855
4627
|
}
|
|
4856
4628
|
};
|
|
4857
4629
|
|
|
@@ -4914,83 +4686,32 @@ function logStep(message) {
|
|
|
4914
4686
|
console.log(import_picocolors.default.cyan(message));
|
|
4915
4687
|
}
|
|
4916
4688
|
async function pickTargetInteractive() {
|
|
4917
|
-
return
|
|
4689
|
+
return dist_default4({
|
|
4918
4690
|
message: "Select install target",
|
|
4919
4691
|
choices: SUPPORTED_TARGETS.map((target) => ({ name: target, value: target }))
|
|
4920
4692
|
});
|
|
4921
4693
|
}
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
unchecked: "( )",
|
|
4925
|
-
cursor: ">"
|
|
4926
|
-
};
|
|
4927
|
-
async function pickModelsInteractive(params) {
|
|
4928
|
-
const choices = params.models.map((model) => ({ name: model, value: model }));
|
|
4929
|
-
if (params.single) {
|
|
4930
|
-
const picked = await dist_default5({
|
|
4931
|
-
message: `${params.title} (${params.hint})`,
|
|
4932
|
-
choices,
|
|
4933
|
-
theme: { icon: { cursor: ASCII_ICONS.cursor } }
|
|
4934
|
-
});
|
|
4935
|
-
return [picked];
|
|
4936
|
-
}
|
|
4937
|
-
return dist_default4({
|
|
4938
|
-
message: `${params.title} (${params.hint})`,
|
|
4939
|
-
choices,
|
|
4940
|
-
theme: { icon: ASCII_ICONS },
|
|
4941
|
-
validate: (picked) => {
|
|
4942
|
-
if (picked.length < 1)
|
|
4943
|
-
return "Pick at least one model.";
|
|
4944
|
-
if (picked.length > params.maxCount)
|
|
4945
|
-
return `Pick at most ${params.maxCount} models.`;
|
|
4946
|
-
return true;
|
|
4947
|
-
}
|
|
4948
|
-
});
|
|
4694
|
+
function hasExplicitModelFlags(options) {
|
|
4695
|
+
return Boolean(options.pmModel || options.strategicModels || options.devModels || options.qcModels || options.otherModels);
|
|
4949
4696
|
}
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
logStep("Step 4/7 - Configure models by role group");
|
|
4961
|
-
return {
|
|
4962
|
-
pm: await pickModelsInteractive({
|
|
4963
|
-
title: "1) project-manager",
|
|
4964
|
-
hint: "orchestrator role, prefer strong agentic model",
|
|
4965
|
-
models,
|
|
4966
|
-
maxCount: 1,
|
|
4967
|
-
single: true
|
|
4968
|
-
}),
|
|
4969
|
-
strategic: await pickModelsInteractive({
|
|
4970
|
-
title: "2) architect / product-manager / prompt-engineer",
|
|
4971
|
-
hint: "decision-heavy roles, prefer high intelligence",
|
|
4972
|
-
models,
|
|
4973
|
-
maxCount: 3
|
|
4974
|
-
}),
|
|
4975
|
-
dev: await pickModelsInteractive({
|
|
4976
|
-
title: "3) fullstack-dev / fullstack-dev-2 / frontend-dev",
|
|
4977
|
-
hint: "prefer coding-focused models",
|
|
4978
|
-
models,
|
|
4979
|
-
maxCount: 3
|
|
4980
|
-
}),
|
|
4981
|
-
qc: await pickModelsInteractive({
|
|
4982
|
-
title: "4) qc-specialist / qc-specialist-2 / qc-specialist-3",
|
|
4983
|
-
hint: "prefer three distinct models",
|
|
4984
|
-
models,
|
|
4985
|
-
maxCount: 3
|
|
4986
|
-
}),
|
|
4987
|
-
others: await pickModelsInteractive({
|
|
4988
|
-
title: "5) other roles",
|
|
4989
|
-
hint: "up to 3 models, random assignment",
|
|
4990
|
-
models,
|
|
4991
|
-
maxCount: 3
|
|
4992
|
-
})
|
|
4697
|
+
function resolveExplicitModelAssignments(options) {
|
|
4698
|
+
const allow = (label, values, max, required) => {
|
|
4699
|
+
if (!values?.length) {
|
|
4700
|
+
if (required)
|
|
4701
|
+
throw new Error(`${label} is required when any --*-model flag is set.`);
|
|
4702
|
+
return [];
|
|
4703
|
+
}
|
|
4704
|
+
if (values.length > max)
|
|
4705
|
+
throw new Error(`${label}: pick at most ${max} model(s).`);
|
|
4706
|
+
return values;
|
|
4993
4707
|
};
|
|
4708
|
+
return buildModelAssignments({
|
|
4709
|
+
pm: allow("pm-model", options.pmModel ? [options.pmModel] : undefined, 1, true),
|
|
4710
|
+
strategic: allow("strategic-models", parseCsv(options.strategicModels), 3, true),
|
|
4711
|
+
dev: allow("dev-models", parseCsv(options.devModels), 3, true),
|
|
4712
|
+
qc: allow("qc-models", parseCsv(options.qcModels), 3, true),
|
|
4713
|
+
others: allow("other-models", parseCsv(options.otherModels), 3, true)
|
|
4714
|
+
});
|
|
4994
4715
|
}
|
|
4995
4716
|
async function runInit2(options) {
|
|
4996
4717
|
const target = options.target || (options.yes ? "opencode" : await pickTargetInteractive());
|
|
@@ -5013,14 +4734,14 @@ async function runInit2(options) {
|
|
|
5013
4734
|
}
|
|
5014
4735
|
return;
|
|
5015
4736
|
}
|
|
5016
|
-
|
|
5017
|
-
const
|
|
5018
|
-
if (
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
logStep("Step
|
|
4737
|
+
const useExplicitModels = hasExplicitModelFlags(options);
|
|
4738
|
+
const assignments = useExplicitModels ? resolveExplicitModelAssignments(options) : {};
|
|
4739
|
+
if (useExplicitModels) {
|
|
4740
|
+
logStep("Step 3/4 - Apply explicit role model overrides from CLI flags");
|
|
4741
|
+
} else {
|
|
4742
|
+
logStep("Step 3/4 - Fast setup (schema + plugin; OpenCode default models)");
|
|
4743
|
+
}
|
|
4744
|
+
logStep("Step 4/4 - Update config");
|
|
5024
4745
|
const configPath = adapter.resolveConfigPath?.(scope, options.output);
|
|
5025
4746
|
if (!configPath)
|
|
5026
4747
|
throw new Error(`Adapter ${target} does not implement config path resolution.`);
|
|
@@ -5028,7 +4749,6 @@ async function runInit2(options) {
|
|
|
5028
4749
|
const updated = adapter.mutateConfigForInit?.(current, assignments);
|
|
5029
4750
|
if (!updated)
|
|
5030
4751
|
throw new Error(`Adapter ${target} does not implement init mutation.`);
|
|
5031
|
-
logStep("Step 7/7 - Self-check");
|
|
5032
4752
|
const checkErrors = adapter.validateConfig?.(updated) || [];
|
|
5033
4753
|
if (checkErrors.length) {
|
|
5034
4754
|
throw new Error(`Configuration verification failed:
|
|
@@ -5049,9 +4769,11 @@ async function runInit2(options) {
|
|
|
5049
4769
|
console.log(`Config file: ${configPath}`);
|
|
5050
4770
|
if (adapter.printPostSetupSummary)
|
|
5051
4771
|
adapter.printPostSetupSummary(updated);
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
4772
|
+
if (Object.keys(assignments).length) {
|
|
4773
|
+
console.log("Assigned roles:");
|
|
4774
|
+
for (const [roleId, modelId] of Object.entries(assignments)) {
|
|
4775
|
+
console.log(` - ${roleId}: ${modelId}`);
|
|
4776
|
+
}
|
|
5055
4777
|
}
|
|
5056
4778
|
}
|
|
5057
4779
|
function runDoctor2(options) {
|
|
@@ -5098,7 +4820,7 @@ function runDoctor2(options) {
|
|
|
5098
4820
|
process.exitCode = 1;
|
|
5099
4821
|
}
|
|
5100
4822
|
program2.name("mstar-harness").description("Morning Star harness CLI for target-based agent bootstrap").version(packageVersion);
|
|
5101
|
-
program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--pm-model <model>", "
|
|
4823
|
+
program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--pm-model <model>", "Optional: model for project-manager (advanced override)").option("--strategic-models <a,b,c>", "Optional: models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Optional: models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Optional: models for qc trio").option("--other-models <a,b,c>", "Optional: models for remaining roles").action(async (options) => {
|
|
5102
4824
|
await runInit2(options);
|
|
5103
4825
|
});
|
|
5104
4826
|
program2.command("doctor").description("Validate Morning Star setup for a target agent config").option("--target <target>", "Target agent for doctor checks", "opencode").option("--scope <scope>", "Config scope: global|project", "project").option("--output <path>", "Config file path override, relative to project root").action((options) => {
|