harnessed 3.3.0 → 3.3.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/README.md +9 -1
- package/dist/cli.mjs +116 -30
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,15 @@ npm install -g harnessed && harnessed setup
|
|
|
36
36
|
|
|
37
37
|
> Windows PowerShell 5.x 不支持 `&&` 链接,需改 `;` 或分两行 (`npm install -g harnessed; harnessed setup`)。bash / zsh / PowerShell 7+ / cmd.exe 都正常。
|
|
38
38
|
|
|
39
|
-
🤖 **或让 AI 帮你装** —
|
|
39
|
+
🤖 **或让 AI 帮你装** — 把下面这句话发给 Claude Code (或任何 AI 助手):
|
|
40
|
+
|
|
41
|
+
> 按 `https://github.com/easyinplay/harnessed/blob/main/INSTALL-WITH-AI.md` 的指导帮我装 harnessed
|
|
42
|
+
|
|
43
|
+
AI 会自动 fetch 文档 + 跑安装,处理 OS / 权限 / PATH / corepack 等 edge case,无需复制大段文字。
|
|
44
|
+
|
|
45
|
+
> [!TIP]
|
|
46
|
+
> 🚀 **很多人关心的 Agent Teams 和 Subagent 功能,在 harnessed 中会根据任务自动启用!**
|
|
47
|
+
> 无需手动配置 `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS`,`harnessed setup` v3.3.1+ 会自动写入 `~/.claude/settings.json`;Pattern A 全栈三路 / Pattern C 4-specialist 等 multi-agent workflow 即开即用。
|
|
40
48
|
|
|
41
49
|
---
|
|
42
50
|
|
package/dist/cli.mjs
CHANGED
|
@@ -847,7 +847,7 @@ var init_resume = __esm({
|
|
|
847
847
|
|
|
848
848
|
// package.json
|
|
849
849
|
var package_default = {
|
|
850
|
-
version: "3.3.
|
|
850
|
+
version: "3.3.1"};
|
|
851
851
|
|
|
852
852
|
// src/manifest/errors.ts
|
|
853
853
|
function instancePathToKeyPath(instancePath) {
|
|
@@ -1633,7 +1633,7 @@ function renderHumanTable(records) {
|
|
|
1633
1633
|
}
|
|
1634
1634
|
}
|
|
1635
1635
|
function pipeToJq(filterExpr, lines) {
|
|
1636
|
-
return new Promise((
|
|
1636
|
+
return new Promise((resolve10, reject) => {
|
|
1637
1637
|
const child = spawn("jq", [filterExpr], {
|
|
1638
1638
|
stdio: ["pipe", "inherit", "inherit"],
|
|
1639
1639
|
windowsHide: true
|
|
@@ -1642,12 +1642,12 @@ function pipeToJq(filterExpr, lines) {
|
|
|
1642
1642
|
const e = err2;
|
|
1643
1643
|
if (e.code === "ENOENT") {
|
|
1644
1644
|
console.error("\u2717 jq not found in PATH \u2014 run: harnessed doctor");
|
|
1645
|
-
|
|
1645
|
+
resolve10(1);
|
|
1646
1646
|
} else {
|
|
1647
1647
|
reject(err2);
|
|
1648
1648
|
}
|
|
1649
1649
|
});
|
|
1650
|
-
child.on("close", (code) =>
|
|
1650
|
+
child.on("close", (code) => resolve10(code ?? 0));
|
|
1651
1651
|
child.stdin.write(lines.join("\n"));
|
|
1652
1652
|
child.stdin.end();
|
|
1653
1653
|
});
|
|
@@ -3364,10 +3364,10 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3364
3364
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed", "preflight");
|
|
3365
3365
|
return { ok: false, phase: "preflight", error: e };
|
|
3366
3366
|
}
|
|
3367
|
-
const
|
|
3367
|
+
const settingsPath2 = join(homedir(), ".claude", "settings.json");
|
|
3368
3368
|
let existing;
|
|
3369
3369
|
try {
|
|
3370
|
-
existing = await readFile(
|
|
3370
|
+
existing = await readFile(settingsPath2, "utf8");
|
|
3371
3371
|
} catch {
|
|
3372
3372
|
existing = null;
|
|
3373
3373
|
}
|
|
@@ -3398,7 +3398,7 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3398
3398
|
const newText = `${JSON.stringify(settings, null, 2)}
|
|
3399
3399
|
`;
|
|
3400
3400
|
const plan = {
|
|
3401
|
-
files: [{ target:
|
|
3401
|
+
files: [{ target: settingsPath2, scope: "HOME", oldText: existing ?? "", newText }]
|
|
3402
3402
|
};
|
|
3403
3403
|
process.stdout.write(renderDiff(plan, ctx));
|
|
3404
3404
|
const conf = await confirmAt("L3", { ...ctx});
|
|
@@ -3409,10 +3409,10 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3409
3409
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
3410
3410
|
const bk = await backup(plan, ctx);
|
|
3411
3411
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
3412
|
-
await writeFile(
|
|
3412
|
+
await writeFile(settingsPath2, newText);
|
|
3413
3413
|
let verify;
|
|
3414
3414
|
try {
|
|
3415
|
-
verify = JSON.parse(await readFile(
|
|
3415
|
+
verify = JSON.parse(await readFile(settingsPath2, "utf8"));
|
|
3416
3416
|
} catch (e) {
|
|
3417
3417
|
return {
|
|
3418
3418
|
ok: false,
|
|
@@ -3440,7 +3440,7 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3440
3440
|
};
|
|
3441
3441
|
}
|
|
3442
3442
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
|
|
3443
|
-
return { ok: true, backupId: bk.backupId, appliedFiles: [
|
|
3443
|
+
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath2] };
|
|
3444
3444
|
};
|
|
3445
3445
|
function getUserClaudeJsonPath() {
|
|
3446
3446
|
return join(homedir(), ".claude.json");
|
|
@@ -3476,7 +3476,7 @@ async function isPluginRegistered(pluginName) {
|
|
|
3476
3476
|
return Object.keys(plugins).some((k) => k.split("@")[0] === pluginName);
|
|
3477
3477
|
}
|
|
3478
3478
|
function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
3479
|
-
return new Promise((
|
|
3479
|
+
return new Promise((resolve10) => {
|
|
3480
3480
|
const isWin = process.platform === "win32";
|
|
3481
3481
|
const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
|
|
3482
3482
|
let stderr = "";
|
|
@@ -3485,15 +3485,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
|
3485
3485
|
});
|
|
3486
3486
|
const timer = setTimeout(() => {
|
|
3487
3487
|
child.kill("SIGKILL");
|
|
3488
|
-
|
|
3488
|
+
resolve10({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
|
|
3489
3489
|
}, timeoutMs);
|
|
3490
3490
|
child.on("error", (e) => {
|
|
3491
3491
|
clearTimeout(timer);
|
|
3492
|
-
|
|
3492
|
+
resolve10({ exitCode: -1, stderr: `${stderr}${e.message}` });
|
|
3493
3493
|
});
|
|
3494
3494
|
child.on("close", (code) => {
|
|
3495
3495
|
clearTimeout(timer);
|
|
3496
|
-
|
|
3496
|
+
resolve10({ exitCode: code ?? -1, stderr });
|
|
3497
3497
|
});
|
|
3498
3498
|
});
|
|
3499
3499
|
}
|
|
@@ -3672,10 +3672,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
3672
3672
|
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
3673
3673
|
stderr += chunk;
|
|
3674
3674
|
});
|
|
3675
|
-
return await new Promise((
|
|
3675
|
+
return await new Promise((resolve10) => {
|
|
3676
3676
|
const timer = setTimeout(() => {
|
|
3677
3677
|
child.kill("SIGKILL");
|
|
3678
|
-
|
|
3678
|
+
resolve10({
|
|
3679
3679
|
ok: false,
|
|
3680
3680
|
phase: "spawn",
|
|
3681
3681
|
error: {
|
|
@@ -3690,7 +3690,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
3690
3690
|
}, effectiveTimeoutMs);
|
|
3691
3691
|
child.on("error", (err2) => {
|
|
3692
3692
|
clearTimeout(timer);
|
|
3693
|
-
|
|
3693
|
+
resolve10({
|
|
3694
3694
|
ok: false,
|
|
3695
3695
|
phase: "spawn",
|
|
3696
3696
|
error: {
|
|
@@ -3705,14 +3705,14 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
3705
3705
|
});
|
|
3706
3706
|
child.on("close", (code) => {
|
|
3707
3707
|
clearTimeout(timer);
|
|
3708
|
-
|
|
3708
|
+
resolve10({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
|
|
3709
3709
|
});
|
|
3710
3710
|
});
|
|
3711
3711
|
}
|
|
3712
3712
|
|
|
3713
3713
|
// src/installers/gitCloneWithSetup.ts
|
|
3714
3714
|
function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
3715
|
-
return new Promise((
|
|
3715
|
+
return new Promise((resolve10) => {
|
|
3716
3716
|
const isWin = process.platform === "win32";
|
|
3717
3717
|
const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
|
|
3718
3718
|
let stdout2 = "";
|
|
@@ -3721,15 +3721,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
|
3721
3721
|
});
|
|
3722
3722
|
const timer = setTimeout(() => {
|
|
3723
3723
|
child.kill("SIGKILL");
|
|
3724
|
-
|
|
3724
|
+
resolve10({ sha: "", exit: -1 });
|
|
3725
3725
|
}, timeoutMs);
|
|
3726
3726
|
child.on("error", () => {
|
|
3727
3727
|
clearTimeout(timer);
|
|
3728
|
-
|
|
3728
|
+
resolve10({ sha: "", exit: -1 });
|
|
3729
3729
|
});
|
|
3730
3730
|
child.on("close", (code) => {
|
|
3731
3731
|
clearTimeout(timer);
|
|
3732
|
-
|
|
3732
|
+
resolve10({ sha: stdout2.trim(), exit: code ?? -1 });
|
|
3733
3733
|
});
|
|
3734
3734
|
});
|
|
3735
3735
|
}
|
|
@@ -4782,6 +4782,80 @@ function registerRollback(program2) {
|
|
|
4782
4782
|
console.log(`restored ${meta.files.length} file(s) from ${timestamp}`);
|
|
4783
4783
|
});
|
|
4784
4784
|
}
|
|
4785
|
+
|
|
4786
|
+
// src/cli/lib/enableAgentTeamsInSettings.ts
|
|
4787
|
+
init_harnessedRoot();
|
|
4788
|
+
function settingsPath() {
|
|
4789
|
+
return resolve(homedir(), ".claude", "settings.json");
|
|
4790
|
+
}
|
|
4791
|
+
async function enableAgentTeamsInSettings() {
|
|
4792
|
+
const path = settingsPath();
|
|
4793
|
+
let raw;
|
|
4794
|
+
try {
|
|
4795
|
+
raw = await readFile(path, "utf8");
|
|
4796
|
+
} catch (err2) {
|
|
4797
|
+
const code = err2.code;
|
|
4798
|
+
if (code !== "ENOENT") {
|
|
4799
|
+
return { status: "warn", message: `read ${path} failed: ${err2.message}` };
|
|
4800
|
+
}
|
|
4801
|
+
return createFreshSettings(path);
|
|
4802
|
+
}
|
|
4803
|
+
let data;
|
|
4804
|
+
try {
|
|
4805
|
+
const parsed = JSON.parse(raw);
|
|
4806
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4807
|
+
return { status: "warn", message: `${path} is not a JSON object` };
|
|
4808
|
+
}
|
|
4809
|
+
data = parsed;
|
|
4810
|
+
} catch (err2) {
|
|
4811
|
+
return { status: "warn", message: `${path} malformed JSON: ${err2.message}` };
|
|
4812
|
+
}
|
|
4813
|
+
const env = data.env ?? {};
|
|
4814
|
+
if (env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1") {
|
|
4815
|
+
return { status: "already-enabled", path };
|
|
4816
|
+
}
|
|
4817
|
+
const backupPath = await backupOriginal(raw);
|
|
4818
|
+
if (backupPath.status === "warn") return backupPath;
|
|
4819
|
+
data.env = { ...env, CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" };
|
|
4820
|
+
const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
|
|
4821
|
+
`);
|
|
4822
|
+
if (writeErr) return { status: "warn", message: writeErr };
|
|
4823
|
+
return { status: "enabled", path, backupPath: backupPath.path };
|
|
4824
|
+
}
|
|
4825
|
+
async function createFreshSettings(path) {
|
|
4826
|
+
const data = { env: { CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: "1" } };
|
|
4827
|
+
try {
|
|
4828
|
+
await mkdir(join(homedir(), ".claude"), { recursive: true });
|
|
4829
|
+
} catch (err2) {
|
|
4830
|
+
return { status: "warn", message: `mkdir ~/.claude failed: ${err2.message}` };
|
|
4831
|
+
}
|
|
4832
|
+
const writeErr = await atomicWrite(path, `${JSON.stringify(data, null, 2)}
|
|
4833
|
+
`);
|
|
4834
|
+
if (writeErr) return { status: "warn", message: writeErr };
|
|
4835
|
+
return { status: "created", path };
|
|
4836
|
+
}
|
|
4837
|
+
async function backupOriginal(raw) {
|
|
4838
|
+
const backupRoot = harnessedSubdir("backups");
|
|
4839
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
4840
|
+
const backupPath = join(backupRoot, `settings.json.${ts}.bak`);
|
|
4841
|
+
try {
|
|
4842
|
+
await mkdir(backupRoot, { recursive: true });
|
|
4843
|
+
await writeFile(backupPath, raw, "utf8");
|
|
4844
|
+
return { status: "ok", path: backupPath };
|
|
4845
|
+
} catch (err2) {
|
|
4846
|
+
return { status: "warn", message: `backup ${backupPath} failed: ${err2.message}` };
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
async function atomicWrite(path, content) {
|
|
4850
|
+
const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
4851
|
+
try {
|
|
4852
|
+
await writeFile(tmpPath, content, "utf8");
|
|
4853
|
+
await rename(tmpPath, path);
|
|
4854
|
+
return void 0;
|
|
4855
|
+
} catch (err2) {
|
|
4856
|
+
return `write ${path} failed: ${err2.message}`;
|
|
4857
|
+
}
|
|
4858
|
+
}
|
|
4785
4859
|
init_checkAgentTeams();
|
|
4786
4860
|
var FLAT_LEGACY_DEPRECATED = /* @__PURE__ */ new Set(["plan-feature", "execute-task", "verify-work"]);
|
|
4787
4861
|
var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro", "auto"]);
|
|
@@ -5004,6 +5078,18 @@ function registerSetup(program2) {
|
|
|
5004
5078
|
`
|
|
5005
5079
|
Step A complete: ${skillsInstalled} workflow skill(s) installed to ${skillsBase}`
|
|
5006
5080
|
);
|
|
5081
|
+
const cResult = await enableAgentTeamsInSettings();
|
|
5082
|
+
if (cResult.status === "created") {
|
|
5083
|
+
console.log(` [C] created ${cResult.path} + enabled Agent Teams`);
|
|
5084
|
+
} else if (cResult.status === "already-enabled") {
|
|
5085
|
+
console.log(` [C] Agent Teams already enabled (${cResult.path})`);
|
|
5086
|
+
} else if (cResult.status === "enabled") {
|
|
5087
|
+
console.log(
|
|
5088
|
+
` [C] enabled Agent Teams in ${cResult.path} (backup saved \u2192 ${cResult.backupPath})`
|
|
5089
|
+
);
|
|
5090
|
+
} else {
|
|
5091
|
+
console.warn(` [C] Agent Teams enable skipped: ${cResult.message}`);
|
|
5092
|
+
}
|
|
5007
5093
|
const manifestPaths = await listBaseManifests2(pkgRoot);
|
|
5008
5094
|
const b = await runStepBInstall(manifestPaths);
|
|
5009
5095
|
const stepBMs = (b.elapsedMs / 1e3).toFixed(1);
|
|
@@ -5092,10 +5178,10 @@ var uninstallCcHookAdd = async (ctx) => {
|
|
|
5092
5178
|
}
|
|
5093
5179
|
const abort = dryRunGate(ctx);
|
|
5094
5180
|
if (abort) return abort;
|
|
5095
|
-
const
|
|
5181
|
+
const settingsPath2 = join(homedir(), ".claude", "settings.json");
|
|
5096
5182
|
let existing;
|
|
5097
5183
|
try {
|
|
5098
|
-
existing = await readFile(
|
|
5184
|
+
existing = await readFile(settingsPath2, "utf8");
|
|
5099
5185
|
} catch {
|
|
5100
5186
|
return { ok: true, removedPaths: [] };
|
|
5101
5187
|
}
|
|
@@ -5124,8 +5210,8 @@ var uninstallCcHookAdd = async (ctx) => {
|
|
|
5124
5210
|
if (settings.hooks?.[ev]?.length === before || before === settings.hooks?.[ev]?.length) ;
|
|
5125
5211
|
const newText = `${JSON.stringify(settings, null, 2)}
|
|
5126
5212
|
`;
|
|
5127
|
-
await writeFile(
|
|
5128
|
-
return { ok: true, removedPaths: [
|
|
5213
|
+
await writeFile(settingsPath2, newText);
|
|
5214
|
+
return { ok: true, removedPaths: [settingsPath2] };
|
|
5129
5215
|
};
|
|
5130
5216
|
|
|
5131
5217
|
// src/uninstallers/ccPluginMarketplace.ts
|
|
@@ -5261,7 +5347,7 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
5261
5347
|
const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
|
|
5262
5348
|
const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
|
|
5263
5349
|
const isWin = process.platform === "win32";
|
|
5264
|
-
const result = await new Promise((
|
|
5350
|
+
const result = await new Promise((resolve10) => {
|
|
5265
5351
|
const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
|
|
5266
5352
|
let stderr = "";
|
|
5267
5353
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -5269,15 +5355,15 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
5269
5355
|
});
|
|
5270
5356
|
const timer = setTimeout(() => {
|
|
5271
5357
|
child.kill("SIGKILL");
|
|
5272
|
-
|
|
5358
|
+
resolve10({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
5273
5359
|
}, 3e4);
|
|
5274
5360
|
child.on("error", (e) => {
|
|
5275
5361
|
clearTimeout(timer);
|
|
5276
|
-
|
|
5362
|
+
resolve10({ exitCode: -1, stderr: e.message });
|
|
5277
5363
|
});
|
|
5278
5364
|
child.on("close", (code) => {
|
|
5279
5365
|
clearTimeout(timer);
|
|
5280
|
-
|
|
5366
|
+
resolve10({ exitCode: code ?? -1, stderr });
|
|
5281
5367
|
});
|
|
5282
5368
|
});
|
|
5283
5369
|
if (result.exitCode !== 0) {
|