mirror of
https://github.com/cexll/myclaude.git
synced 2026-02-27 09:13:04 +08:00
fix installer bootstrap for do/omo/dev initialization
This commit is contained in:
107
bin/cli.js
107
bin/cli.js
@@ -15,6 +15,8 @@ const API_HEADERS = {
|
||||
"User-Agent": "myclaude-npx",
|
||||
Accept: "application/vnd.github+json",
|
||||
};
|
||||
const WRAPPER_REQUIRED_MODULES = new Set(["do", "omo"]);
|
||||
const WRAPPER_REQUIRED_SKILLS = new Set(["dev"]);
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = {
|
||||
@@ -499,9 +501,19 @@ async function updateInstalledModules(installDir, tag, config, dryRun) {
|
||||
}
|
||||
|
||||
await fs.promises.mkdir(installDir, { recursive: true });
|
||||
const installState = { wrapperInstalled: false };
|
||||
|
||||
async function ensureWrapperInstalled() {
|
||||
if (installState.wrapperInstalled) return;
|
||||
process.stdout.write("Installing codeagent-wrapper...\n");
|
||||
await runInstallSh(repoRoot, installDir, tag);
|
||||
installState.wrapperInstalled = true;
|
||||
}
|
||||
|
||||
for (const name of toUpdate) {
|
||||
if (WRAPPER_REQUIRED_MODULES.has(name)) await ensureWrapperInstalled();
|
||||
process.stdout.write(`Updating module: ${name}\n`);
|
||||
const r = await applyModule(name, config, repoRoot, installDir, true, tag);
|
||||
const r = await applyModule(name, config, repoRoot, installDir, true, tag, installState);
|
||||
upsertModuleStatus(installDir, r);
|
||||
}
|
||||
} finally {
|
||||
@@ -777,7 +789,57 @@ async function rmTree(p) {
|
||||
await fs.promises.rmdir(p, { recursive: true });
|
||||
}
|
||||
|
||||
async function applyModule(moduleName, config, repoRoot, installDir, force, tag) {
|
||||
function defaultModelsConfig() {
|
||||
return {
|
||||
default_backend: "codex",
|
||||
default_model: "gpt-4.1",
|
||||
backends: {},
|
||||
agents: {},
|
||||
};
|
||||
}
|
||||
|
||||
function mergeModuleAgentsToModels(moduleName, mod, repoRoot) {
|
||||
const moduleAgents = mod && mod.agents;
|
||||
if (!isPlainObject(moduleAgents) || !Object.keys(moduleAgents).length) return false;
|
||||
|
||||
const modelsPath = path.join(os.homedir(), ".codeagent", "models.json");
|
||||
fs.mkdirSync(path.dirname(modelsPath), { recursive: true });
|
||||
|
||||
let models;
|
||||
if (fs.existsSync(modelsPath)) {
|
||||
models = JSON.parse(fs.readFileSync(modelsPath, "utf8"));
|
||||
} else {
|
||||
const templatePath = path.join(repoRoot, "templates", "models.json.example");
|
||||
if (fs.existsSync(templatePath)) {
|
||||
models = JSON.parse(fs.readFileSync(templatePath, "utf8"));
|
||||
if (!isPlainObject(models)) models = defaultModelsConfig();
|
||||
models.agents = {};
|
||||
} else {
|
||||
models = defaultModelsConfig();
|
||||
}
|
||||
}
|
||||
|
||||
if (!isPlainObject(models)) models = defaultModelsConfig();
|
||||
if (!isPlainObject(models.agents)) models.agents = {};
|
||||
|
||||
let modified = false;
|
||||
for (const [agentName, agentCfg] of Object.entries(moduleAgents)) {
|
||||
if (!isPlainObject(agentCfg)) continue;
|
||||
const existing = models.agents[agentName];
|
||||
const canOverwrite = !isPlainObject(existing) || Object.prototype.hasOwnProperty.call(existing, "__module__");
|
||||
if (!canOverwrite) continue;
|
||||
const next = { ...agentCfg, __module__: moduleName };
|
||||
if (!deepEqual(existing, next)) {
|
||||
models.agents[agentName] = next;
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modified) fs.writeFileSync(modelsPath, JSON.stringify(models, null, 2) + "\n", "utf8");
|
||||
return modified;
|
||||
}
|
||||
|
||||
async function applyModule(moduleName, config, repoRoot, installDir, force, tag, installState) {
|
||||
const mod = config && config.modules && config.modules[moduleName];
|
||||
if (!mod) throw new Error(`Unknown module: ${moduleName}`);
|
||||
const ops = Array.isArray(mod.operations) ? mod.operations : [];
|
||||
@@ -803,7 +865,12 @@ async function applyModule(moduleName, config, repoRoot, installDir, force, tag)
|
||||
if (cmd !== "bash install.sh") {
|
||||
throw new Error(`Refusing run_command: ${cmd || "(empty)"}`);
|
||||
}
|
||||
if (installState && installState.wrapperInstalled) {
|
||||
result.operations.push({ type, status: "success", skipped: true });
|
||||
continue;
|
||||
}
|
||||
await runInstallSh(repoRoot, installDir, tag);
|
||||
if (installState) installState.wrapperInstalled = true;
|
||||
} else {
|
||||
throw new Error(`Unsupported operation type: ${type}`);
|
||||
}
|
||||
@@ -834,6 +901,19 @@ async function applyModule(moduleName, config, repoRoot, installDir, force, tag)
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
if (mergeModuleAgentsToModels(moduleName, mod, repoRoot)) {
|
||||
result.has_agents = true;
|
||||
result.operations.push({ type: "merge_agents", status: "success" });
|
||||
}
|
||||
} catch (err) {
|
||||
result.operations.push({
|
||||
type: "merge_agents",
|
||||
status: "failed",
|
||||
error: err && err.message ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1023,20 +1103,37 @@ async function installSelected(picks, tag, config, installDir, force, dryRun) {
|
||||
}
|
||||
|
||||
await fs.promises.mkdir(installDir, { recursive: true });
|
||||
const installState = { wrapperInstalled: false };
|
||||
|
||||
async function ensureWrapperInstalled() {
|
||||
if (installState.wrapperInstalled) return;
|
||||
process.stdout.write("Installing codeagent-wrapper...\n");
|
||||
await runInstallSh(repoRoot, installDir, tag);
|
||||
installState.wrapperInstalled = true;
|
||||
}
|
||||
|
||||
for (const p of picks) {
|
||||
if (p.kind === "wrapper") {
|
||||
process.stdout.write("Installing codeagent-wrapper...\n");
|
||||
await runInstallSh(repoRoot, installDir, tag);
|
||||
await ensureWrapperInstalled();
|
||||
continue;
|
||||
}
|
||||
if (p.kind === "module") {
|
||||
if (WRAPPER_REQUIRED_MODULES.has(p.moduleName)) await ensureWrapperInstalled();
|
||||
process.stdout.write(`Installing module: ${p.moduleName}\n`);
|
||||
const r = await applyModule(p.moduleName, config, repoRoot, installDir, force, tag);
|
||||
const r = await applyModule(
|
||||
p.moduleName,
|
||||
config,
|
||||
repoRoot,
|
||||
installDir,
|
||||
force,
|
||||
tag,
|
||||
installState
|
||||
);
|
||||
upsertModuleStatus(installDir, r);
|
||||
continue;
|
||||
}
|
||||
if (p.kind === "skill") {
|
||||
if (WRAPPER_REQUIRED_SKILLS.has(p.skillName)) await ensureWrapperInstalled();
|
||||
process.stdout.write(`Installing skill: ${p.skillName}\n`);
|
||||
await copyDirRecursive(
|
||||
path.join(repoRoot, "skills", p.skillName),
|
||||
|
||||
44
install.py
44
install.py
@@ -24,6 +24,7 @@ except ImportError: # pragma: no cover
|
||||
|
||||
DEFAULT_INSTALL_DIR = "~/.claude"
|
||||
SETTINGS_FILE = "settings.json"
|
||||
WRAPPER_REQUIRED_MODULES = {"do", "omo"}
|
||||
|
||||
|
||||
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[
|
||||
"installed_at": datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
if name in WRAPPER_REQUIRED_MODULES:
|
||||
try:
|
||||
ensure_wrapper_installed(ctx)
|
||||
result["operations"].append({"type": "ensure_wrapper", "status": "success"})
|
||||
except Exception as exc: # noqa: BLE001
|
||||
result["status"] = "failed"
|
||||
result["operations"].append(
|
||||
{"type": "ensure_wrapper", "status": "failed", "error": str(exc)}
|
||||
)
|
||||
write_log(
|
||||
{
|
||||
"level": "ERROR",
|
||||
"message": f"Module {name} failed on ensure_wrapper: {exc}",
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
raise
|
||||
|
||||
for op in cfg.get("operations", []):
|
||||
op_type = op.get("type")
|
||||
try:
|
||||
@@ -1081,8 +1100,13 @@ def op_run_command(op: Dict[str, Any], ctx: Dict[str, Any]) -> None:
|
||||
for key, value in op.get("env", {}).items():
|
||||
env[key] = value.replace("${install_dir}", str(ctx["install_dir"]))
|
||||
|
||||
command = op.get("command", "")
|
||||
if sys.platform == "win32" and command.strip() == "bash install.sh":
|
||||
raw_command = str(op.get("command", "")).strip()
|
||||
if raw_command == "bash install.sh" and ctx.get("_wrapper_installed"):
|
||||
write_log({"level": "INFO", "message": "Skip wrapper install; already installed in this run"}, ctx)
|
||||
return
|
||||
|
||||
command = raw_command
|
||||
if sys.platform == "win32" and raw_command == "bash install.sh":
|
||||
command = "cmd /c install.bat"
|
||||
|
||||
# Stream output in real-time while capturing for logging
|
||||
@@ -1156,6 +1180,22 @@ def op_run_command(op: Dict[str, Any], ctx: Dict[str, Any]) -> None:
|
||||
if process.returncode != 0:
|
||||
raise RuntimeError(f"Command failed with code {process.returncode}: {command}")
|
||||
|
||||
if raw_command == "bash install.sh":
|
||||
ctx["_wrapper_installed"] = True
|
||||
|
||||
|
||||
def ensure_wrapper_installed(ctx: Dict[str, Any]) -> None:
|
||||
if ctx.get("_wrapper_installed"):
|
||||
return
|
||||
op_run_command(
|
||||
{
|
||||
"type": "run_command",
|
||||
"command": "bash install.sh",
|
||||
"env": {"INSTALL_DIR": "${install_dir}"},
|
||||
},
|
||||
ctx,
|
||||
)
|
||||
|
||||
|
||||
def write_log(entry: Dict[str, Any], ctx: Dict[str, Any]) -> None:
|
||||
log_path = Path(ctx["log_file"])
|
||||
|
||||
Reference in New Issue
Block a user