fix(cli): improve skill install failure output
This commit is contained in:
@@ -29,6 +29,42 @@ export type SkillInstallResult = {
|
|||||||
code: number | null;
|
code: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function summarizeInstallOutput(text: string): string | undefined {
|
||||||
|
const raw = text.trim();
|
||||||
|
if (!raw) return undefined;
|
||||||
|
const lines = raw
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (lines.length === 0) return undefined;
|
||||||
|
|
||||||
|
const preferred =
|
||||||
|
lines.find((line) => /^error\b/i.test(line)) ??
|
||||||
|
lines.find((line) => /\b(err!|error:|failed)\b/i.test(line)) ??
|
||||||
|
lines.at(-1);
|
||||||
|
|
||||||
|
if (!preferred) return undefined;
|
||||||
|
const normalized = preferred.replace(/\s+/g, " ").trim();
|
||||||
|
const maxLen = 200;
|
||||||
|
return normalized.length > maxLen
|
||||||
|
? `${normalized.slice(0, maxLen - 1)}…`
|
||||||
|
: normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatInstallFailureMessage(result: {
|
||||||
|
code: number | null;
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
}): string {
|
||||||
|
const code =
|
||||||
|
typeof result.code === "number" ? `exit ${result.code}` : "unknown exit";
|
||||||
|
const summary =
|
||||||
|
summarizeInstallOutput(result.stderr) ??
|
||||||
|
summarizeInstallOutput(result.stdout);
|
||||||
|
if (!summary) return `Install failed (${code})`;
|
||||||
|
return `Install failed (${code}): ${summary}`;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveInstallId(spec: SkillInstallSpec, index: number): string {
|
function resolveInstallId(spec: SkillInstallSpec, index: number): string {
|
||||||
return (spec.id ?? `${spec.kind}-${index}`).trim();
|
return (spec.id ?? `${spec.kind}-${index}`).trim();
|
||||||
}
|
}
|
||||||
@@ -91,7 +127,9 @@ function buildInstallCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resolveBrewBinDir(timeoutMs: number): Promise<string | undefined> {
|
async function resolveBrewBinDir(
|
||||||
|
timeoutMs: number,
|
||||||
|
): Promise<string | undefined> {
|
||||||
if (!hasBinary("brew")) return undefined;
|
if (!hasBinary("brew")) return undefined;
|
||||||
const prefixResult = await runCommandWithTimeout(["brew", "--prefix"], {
|
const prefixResult = await runCommandWithTimeout(["brew", "--prefix"], {
|
||||||
timeoutMs: Math.min(timeoutMs, 30_000),
|
timeoutMs: Math.min(timeoutMs, 30_000),
|
||||||
@@ -204,9 +242,12 @@ export async function installSkill(
|
|||||||
|
|
||||||
if (spec.kind === "go" && !hasBinary("go")) {
|
if (spec.kind === "go" && !hasBinary("go")) {
|
||||||
if (hasBinary("brew")) {
|
if (hasBinary("brew")) {
|
||||||
const brewResult = await runCommandWithTimeout(["brew", "install", "go"], {
|
const brewResult = await runCommandWithTimeout(
|
||||||
timeoutMs,
|
["brew", "install", "go"],
|
||||||
});
|
{
|
||||||
|
timeoutMs,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (brewResult.code !== 0) {
|
if (brewResult.code !== 0) {
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
@@ -252,7 +293,7 @@ export async function installSkill(
|
|||||||
const success = result.code === 0;
|
const success = result.code === 0;
|
||||||
return {
|
return {
|
||||||
ok: success,
|
ok: success,
|
||||||
message: success ? "Installed" : "Install failed",
|
message: success ? "Installed" : formatInstallFailureMessage(result),
|
||||||
stdout: result.stdout.trim(),
|
stdout: result.stdout.trim(),
|
||||||
stderr: result.stderr.trim(),
|
stderr: result.stderr.trim(),
|
||||||
code: result.code,
|
code: result.code,
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ import type { ClawdisConfig } from "../config/config.js";
|
|||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { guardCancel, resolveNodeManagerOptions } from "./onboard-helpers.js";
|
import { guardCancel, resolveNodeManagerOptions } from "./onboard-helpers.js";
|
||||||
|
|
||||||
|
function summarizeInstallFailure(message: string): string | undefined {
|
||||||
|
const cleaned = message
|
||||||
|
.replace(/^Install failed(?:\s*\([^)]*\))?\s*:?\s*/i, "")
|
||||||
|
.trim();
|
||||||
|
if (!cleaned) return undefined;
|
||||||
|
const maxLen = 140;
|
||||||
|
return cleaned.length > maxLen ? `${cleaned.slice(0, maxLen - 1)}…` : cleaned;
|
||||||
|
}
|
||||||
|
|
||||||
function upsertSkillEntry(
|
function upsertSkillEntry(
|
||||||
cfg: ClawdisConfig,
|
cfg: ClawdisConfig,
|
||||||
skillKey: string,
|
skillKey: string,
|
||||||
@@ -108,9 +117,19 @@ export async function setupSkills(
|
|||||||
installId,
|
installId,
|
||||||
config: next,
|
config: next,
|
||||||
});
|
});
|
||||||
spin.stop(result.ok ? `Installed ${name}` : `Install failed: ${name}`);
|
if (result.ok) {
|
||||||
if (!result.ok && result.stderr) {
|
spin.stop(`Installed ${name}`);
|
||||||
runtime.log(result.stderr.trim());
|
} else {
|
||||||
|
const code = result.code == null ? "" : ` (exit ${result.code})`;
|
||||||
|
const detail = summarizeInstallFailure(result.message);
|
||||||
|
spin.stop(
|
||||||
|
`Install failed: ${name}${code}${detail ? ` — ${detail}` : ""}`,
|
||||||
|
);
|
||||||
|
if (result.stderr) runtime.log(result.stderr.trim());
|
||||||
|
else if (result.stdout) runtime.log(result.stdout.trim());
|
||||||
|
runtime.log(
|
||||||
|
"Tip: run `clawdis doctor` to review skills + requirements.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user