Skip to content

Commit 19d411a

Browse files
committed
fix installer bootstrap for do/omo/dev initialization
1 parent 791bd03 commit 19d411a

2 files changed

Lines changed: 144 additions & 7 deletions

File tree

bin/cli.js

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const API_HEADERS = {
1515
"User-Agent": "myclaude-npx",
1616
Accept: "application/vnd.github+json",
1717
};
18+
const WRAPPER_REQUIRED_MODULES = new Set(["do", "omo"]);
19+
const WRAPPER_REQUIRED_SKILLS = new Set(["dev"]);
1820

1921
function parseArgs(argv) {
2022
const out = {
@@ -499,9 +501,19 @@ async function updateInstalledModules(installDir, tag, config, dryRun) {
499501
}
500502

501503
await fs.promises.mkdir(installDir, { recursive: true });
504+
const installState = { wrapperInstalled: false };
505+
506+
async function ensureWrapperInstalled() {
507+
if (installState.wrapperInstalled) return;
508+
process.stdout.write("Installing codeagent-wrapper...\n");
509+
await runInstallSh(repoRoot, installDir, tag);
510+
installState.wrapperInstalled = true;
511+
}
512+
502513
for (const name of toUpdate) {
514+
if (WRAPPER_REQUIRED_MODULES.has(name)) await ensureWrapperInstalled();
503515
process.stdout.write(`Updating module: ${name}\n`);
504-
const r = await applyModule(name, config, repoRoot, installDir, true, tag);
516+
const r = await applyModule(name, config, repoRoot, installDir, true, tag, installState);
505517
upsertModuleStatus(installDir, r);
506518
}
507519
} finally {
@@ -777,7 +789,57 @@ async function rmTree(p) {
777789
await fs.promises.rmdir(p, { recursive: true });
778790
}
779791

780-
async function applyModule(moduleName, config, repoRoot, installDir, force, tag) {
792+
function defaultModelsConfig() {
793+
return {
794+
default_backend: "codex",
795+
default_model: "gpt-4.1",
796+
backends: {},
797+
agents: {},
798+
};
799+
}
800+
801+
function mergeModuleAgentsToModels(moduleName, mod, repoRoot) {
802+
const moduleAgents = mod && mod.agents;
803+
if (!isPlainObject(moduleAgents) || !Object.keys(moduleAgents).length) return false;
804+
805+
const modelsPath = path.join(os.homedir(), ".codeagent", "models.json");
806+
fs.mkdirSync(path.dirname(modelsPath), { recursive: true });
807+
808+
let models;
809+
if (fs.existsSync(modelsPath)) {
810+
models = JSON.parse(fs.readFileSync(modelsPath, "utf8"));
811+
} else {
812+
const templatePath = path.join(repoRoot, "templates", "models.json.example");
813+
if (fs.existsSync(templatePath)) {
814+
models = JSON.parse(fs.readFileSync(templatePath, "utf8"));
815+
if (!isPlainObject(models)) models = defaultModelsConfig();
816+
models.agents = {};
817+
} else {
818+
models = defaultModelsConfig();
819+
}
820+
}
821+
822+
if (!isPlainObject(models)) models = defaultModelsConfig();
823+
if (!isPlainObject(models.agents)) models.agents = {};
824+
825+
let modified = false;
826+
for (const [agentName, agentCfg] of Object.entries(moduleAgents)) {
827+
if (!isPlainObject(agentCfg)) continue;
828+
const existing = models.agents[agentName];
829+
const canOverwrite = !isPlainObject(existing) || Object.prototype.hasOwnProperty.call(existing, "__module__");
830+
if (!canOverwrite) continue;
831+
const next = { ...agentCfg, __module__: moduleName };
832+
if (!deepEqual(existing, next)) {
833+
models.agents[agentName] = next;
834+
modified = true;
835+
}
836+
}
837+
838+
if (modified) fs.writeFileSync(modelsPath, JSON.stringify(models, null, 2) + "\n", "utf8");
839+
return modified;
840+
}
841+
842+
async function applyModule(moduleName, config, repoRoot, installDir, force, tag, installState) {
781843
const mod = config && config.modules && config.modules[moduleName];
782844
if (!mod) throw new Error(`Unknown module: ${moduleName}`);
783845
const ops = Array.isArray(mod.operations) ? mod.operations : [];
@@ -803,7 +865,12 @@ async function applyModule(moduleName, config, repoRoot, installDir, force, tag)
803865
if (cmd !== "bash install.sh") {
804866
throw new Error(`Refusing run_command: ${cmd || "(empty)"}`);
805867
}
868+
if (installState && installState.wrapperInstalled) {
869+
result.operations.push({ type, status: "success", skipped: true });
870+
continue;
871+
}
806872
await runInstallSh(repoRoot, installDir, tag);
873+
if (installState) installState.wrapperInstalled = true;
807874
} else {
808875
throw new Error(`Unsupported operation type: ${type}`);
809876
}
@@ -834,6 +901,19 @@ async function applyModule(moduleName, config, repoRoot, installDir, force, tag)
834901
});
835902
}
836903

904+
try {
905+
if (mergeModuleAgentsToModels(moduleName, mod, repoRoot)) {
906+
result.has_agents = true;
907+
result.operations.push({ type: "merge_agents", status: "success" });
908+
}
909+
} catch (err) {
910+
result.operations.push({
911+
type: "merge_agents",
912+
status: "failed",
913+
error: err && err.message ? err.message : String(err),
914+
});
915+
}
916+
837917
return result;
838918
}
839919

@@ -1023,20 +1103,37 @@ async function installSelected(picks, tag, config, installDir, force, dryRun) {
10231103
}
10241104

10251105
await fs.promises.mkdir(installDir, { recursive: true });
1106+
const installState = { wrapperInstalled: false };
1107+
1108+
async function ensureWrapperInstalled() {
1109+
if (installState.wrapperInstalled) return;
1110+
process.stdout.write("Installing codeagent-wrapper...\n");
1111+
await runInstallSh(repoRoot, installDir, tag);
1112+
installState.wrapperInstalled = true;
1113+
}
10261114

10271115
for (const p of picks) {
10281116
if (p.kind === "wrapper") {
1029-
process.stdout.write("Installing codeagent-wrapper...\n");
1030-
await runInstallSh(repoRoot, installDir, tag);
1117+
await ensureWrapperInstalled();
10311118
continue;
10321119
}
10331120
if (p.kind === "module") {
1121+
if (WRAPPER_REQUIRED_MODULES.has(p.moduleName)) await ensureWrapperInstalled();
10341122
process.stdout.write(`Installing module: ${p.moduleName}\n`);
1035-
const r = await applyModule(p.moduleName, config, repoRoot, installDir, force, tag);
1123+
const r = await applyModule(
1124+
p.moduleName,
1125+
config,
1126+
repoRoot,
1127+
installDir,
1128+
force,
1129+
tag,
1130+
installState
1131+
);
10361132
upsertModuleStatus(installDir, r);
10371133
continue;
10381134
}
10391135
if (p.kind === "skill") {
1136+
if (WRAPPER_REQUIRED_SKILLS.has(p.skillName)) await ensureWrapperInstalled();
10401137
process.stdout.write(`Installing skill: ${p.skillName}\n`);
10411138
await copyDirRecursive(
10421139
path.join(repoRoot, "skills", p.skillName),

install.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
DEFAULT_INSTALL_DIR = "~/.claude"
2626
SETTINGS_FILE = "settings.json"
27+
WRAPPER_REQUIRED_MODULES = {"do", "omo"}
2728

2829

2930
def _ensure_list(ctx: Dict[str, Any], key: str) -> List[Any]:
@@ -898,6 +899,24 @@ def execute_module(name: str, cfg: Dict[str, Any], ctx: Dict[str, Any]) -> Dict[
898899
"installed_at": datetime.now().isoformat(),
899900
}
900901

902+
if name in WRAPPER_REQUIRED_MODULES:
903+
try:
904+
ensure_wrapper_installed(ctx)
905+
result["operations"].append({"type": "ensure_wrapper", "status": "success"})
906+
except Exception as exc: # noqa: BLE001
907+
result["status"] = "failed"
908+
result["operations"].append(
909+
{"type": "ensure_wrapper", "status": "failed", "error": str(exc)}
910+
)
911+
write_log(
912+
{
913+
"level": "ERROR",
914+
"message": f"Module {name} failed on ensure_wrapper: {exc}",
915+
},
916+
ctx,
917+
)
918+
raise
919+
901920
for op in cfg.get("operations", []):
902921
op_type = op.get("type")
903922
try:
@@ -1081,8 +1100,13 @@ def op_run_command(op: Dict[str, Any], ctx: Dict[str, Any]) -> None:
10811100
for key, value in op.get("env", {}).items():
10821101
env[key] = value.replace("${install_dir}", str(ctx["install_dir"]))
10831102

1084-
command = op.get("command", "")
1085-
if sys.platform == "win32" and command.strip() == "bash install.sh":
1103+
raw_command = str(op.get("command", "")).strip()
1104+
if raw_command == "bash install.sh" and ctx.get("_wrapper_installed"):
1105+
write_log({"level": "INFO", "message": "Skip wrapper install; already installed in this run"}, ctx)
1106+
return
1107+
1108+
command = raw_command
1109+
if sys.platform == "win32" and raw_command == "bash install.sh":
10861110
command = "cmd /c install.bat"
10871111

10881112
# Stream output in real-time while capturing for logging
@@ -1156,6 +1180,22 @@ def read_output(pipe, lines, file=None):
11561180
if process.returncode != 0:
11571181
raise RuntimeError(f"Command failed with code {process.returncode}: {command}")
11581182

1183+
if raw_command == "bash install.sh":
1184+
ctx["_wrapper_installed"] = True
1185+
1186+
1187+
def ensure_wrapper_installed(ctx: Dict[str, Any]) -> None:
1188+
if ctx.get("_wrapper_installed"):
1189+
return
1190+
op_run_command(
1191+
{
1192+
"type": "run_command",
1193+
"command": "bash install.sh",
1194+
"env": {"INSTALL_DIR": "${install_dir}"},
1195+
},
1196+
ctx,
1197+
)
1198+
11591199

11601200
def write_log(entry: Dict[str, Any], ctx: Dict[str, Any]) -> None:
11611201
log_path = Path(ctx["log_file"])

0 commit comments

Comments
 (0)