harnessed 3.2.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 +45 -36
- package/dist/cli.mjs +125 -67
- 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/workflows/auto/SKILL.md +4 -6
- package/workflows/auto/workflow.yaml +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
|
|
|
@@ -344,6 +352,42 @@ planning-with-files /plan (cross-cutting tool) → write artifacts to .planning/
|
|
|
344
352
|
|
|
345
353
|
---
|
|
346
354
|
|
|
355
|
+
## 🛠️ 维护命令 (Operational)
|
|
356
|
+
|
|
357
|
+
> 这些是 harnessed 自身维护命令(setup / 健康检查 / 备份回滚 / 状态恢复等),日常 feature 开发用上面的 slash command 即可,这块通常不需要。
|
|
358
|
+
|
|
359
|
+
### CLI 命令
|
|
360
|
+
|
|
361
|
+
| 命令 | 说明 |
|
|
362
|
+
| ---- | ---- |
|
|
363
|
+
| `harnessed setup` | 一次性 setup,装 workflow skills 到 `~/.claude/skills/` + MCP 到 `~/.claude.json` |
|
|
364
|
+
| `harnessed resume` | session 中断后恢复至最近 checkpoint |
|
|
365
|
+
| `harnessed status` | 当前 phase + lock holder |
|
|
366
|
+
| `harnessed doctor` | 8-check 健康检查 (Node / MCP / jq / Win bash / 路由 / token budget 等) |
|
|
367
|
+
| `harnessed install <name>` | 装上游 manifest |
|
|
368
|
+
| `harnessed uninstall <name>` | 反向卸载 |
|
|
369
|
+
| `harnessed backup` | snapshot 备份管理 |
|
|
370
|
+
| `harnessed rollback <timestamp>` | 一行回滚 (EOL preserve + sha1 verify) |
|
|
371
|
+
| `harnessed gc` | 清理过期 backups |
|
|
372
|
+
| `harnessed audit-log` | 路由透明日志 query (支持 `--filter` jq 表达式) |
|
|
373
|
+
|
|
374
|
+
### 参数 (Flags)
|
|
375
|
+
|
|
376
|
+
> 所有命令默认 **apply (immediate write)**,无需加 flag。高级用户可加 `--dry-run` 预览。
|
|
377
|
+
|
|
378
|
+
| Flag | 说明 |
|
|
379
|
+
| ---- | ---- |
|
|
380
|
+
| `--dry-run` | 预览不写盘 (高级用户 opt-in) |
|
|
381
|
+
| `--non-interactive` | CI / 脚本场景 |
|
|
382
|
+
| `--system` | L4 全局装允许 (否则降级 L1 npx ephemeral) |
|
|
383
|
+
| `--yes` | uninstall 跳过交互 confirm |
|
|
384
|
+
| `--full-diff` | 展开 > 200 行的 diff 折叠 |
|
|
385
|
+
| `--no-color` | 强制 nocolor (即使 TTY) |
|
|
386
|
+
|
|
387
|
+
> `--apply` flag 仍保留为向后兼容 alias (no-op, 旧脚本不破)。
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
347
391
|
## ❓ FAQ
|
|
348
392
|
|
|
349
393
|
<details>
|
|
@@ -423,41 +467,6 @@ harnessed setup --apply # 自动装齐 gstack + GSD + superpowers + planning-wi
|
|
|
423
467
|
|
|
424
468
|
---
|
|
425
469
|
|
|
426
|
-
## 🛠️ 维护命令 (Operational)
|
|
427
|
-
|
|
428
|
-
> 这些是 harnessed 自身维护命令(setup / 健康检查 / 备份回滚 / 状态恢复等),日常 feature 开发用上面的 slash command 即可,这块通常不需要。
|
|
429
|
-
|
|
430
|
-
### CLI 命令
|
|
431
|
-
|
|
432
|
-
| 命令 | 说明 |
|
|
433
|
-
| ---- | ---- |
|
|
434
|
-
| `harnessed setup` | 一次性 setup,装 workflow skills 到 `~/.claude/skills/` + MCP 到 `~/.claude.json` |
|
|
435
|
-
| `harnessed resume` | session 中断后恢复至最近 checkpoint |
|
|
436
|
-
| `harnessed status` | 当前 phase + lock holder |
|
|
437
|
-
| `harnessed doctor` | 8-check 健康检查 (Node / MCP / jq / Win bash / 路由 / token budget 等) |
|
|
438
|
-
| `harnessed install <name>` | 装上游 manifest |
|
|
439
|
-
| `harnessed uninstall <name>` | 反向卸载 |
|
|
440
|
-
| `harnessed backup` | snapshot 备份管理 |
|
|
441
|
-
| `harnessed rollback <timestamp>` | 一行回滚 (EOL preserve + sha1 verify) |
|
|
442
|
-
| `harnessed gc` | 清理过期 backups |
|
|
443
|
-
| `harnessed audit-log` | 路由透明日志 query (支持 `--filter` jq 表达式) |
|
|
444
|
-
|
|
445
|
-
### 参数 (Flags)
|
|
446
|
-
|
|
447
|
-
> 所有命令默认 **apply (immediate write)**,无需加 flag。高级用户可加 `--dry-run` 预览。
|
|
448
|
-
|
|
449
|
-
| Flag | 说明 |
|
|
450
|
-
| ---- | ---- |
|
|
451
|
-
| `--dry-run` | 预览不写盘 (高级用户 opt-in) |
|
|
452
|
-
| `--non-interactive` | CI / 脚本场景 |
|
|
453
|
-
| `--system` | L4 全局装允许 (否则降级 L1 npx ephemeral) |
|
|
454
|
-
| `--yes` | uninstall 跳过交互 confirm |
|
|
455
|
-
| `--full-diff` | 展开 > 200 行的 diff 折叠 |
|
|
456
|
-
| `--no-color` | 强制 nocolor (即使 TTY) |
|
|
457
|
-
|
|
458
|
-
> `--apply` flag 仍保留为向后兼容 alias (no-op, 旧脚本不破)。
|
|
459
|
-
|
|
460
|
-
---
|
|
461
470
|
|
|
462
471
|
## License
|
|
463
472
|
|
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.
|
|
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
|
});
|
|
@@ -2999,26 +2999,11 @@ function loadPhases(yamlPath, vars) {
|
|
|
2999
2999
|
return validated;
|
|
3000
3000
|
}
|
|
3001
3001
|
|
|
3002
|
-
// src/cli/lib/validateFlags.ts
|
|
3003
|
-
function validateNonInteractiveFlags(raw, cmdName) {
|
|
3004
|
-
if (raw.nonInteractive && !raw.apply && !raw.dryRun) {
|
|
3005
|
-
console.error(
|
|
3006
|
-
`error: --non-interactive requires --apply or --dry-run
|
|
3007
|
-
fix: 'harnessed ${cmdName} --non-interactive --dry-run' or '--apply'`
|
|
3008
|
-
);
|
|
3009
|
-
process.exit(2);
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3012
|
-
|
|
3013
3002
|
// src/cli/execute-task.ts
|
|
3014
3003
|
function registerExecuteTask(program2) {
|
|
3015
3004
|
program2.command("execute-task").description(
|
|
3016
3005
|
"Run execute-task workflow (4-phase chain \u2192 ralph-loop COMPLETE; immediate by default \u2014 use --dry-run for preview)"
|
|
3017
|
-
).requiredOption("--task <text>", "task description (required)").option("--workflow <name>", "workflow name", "execute-task").option(
|
|
3018
|
-
"--apply",
|
|
3019
|
-
"(deprecated; kept for backward compat \u2014 execute-task spawns immediately by default)"
|
|
3020
|
-
).option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "CI / scripts \u2014 requires --apply or --dry-run").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").option("--model-tier <tier>", "override: 'inherit' bypasses per-phase phase.model (B-10)").option("--max-iterations <n>", "ralph-loop max iter (default 20)", (v) => parseInt(v, 10)).action(async (raw) => {
|
|
3021
|
-
validateNonInteractiveFlags(raw, "execute-task --task <text>");
|
|
3006
|
+
).requiredOption("--task <text>", "task description (required)").option("--workflow <name>", "workflow name", "execute-task").option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "CI / scripts").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").option("--model-tier <tier>", "override: 'inherit' bypasses per-phase phase.model (B-10)").option("--max-iterations <n>", "ralph-loop max iter (default 20)", (v) => parseInt(v, 10)).action(async (raw) => {
|
|
3022
3007
|
if (!raw.task) {
|
|
3023
3008
|
console.error("error: --task <text> is required");
|
|
3024
3009
|
process.exit(2);
|
|
@@ -3065,7 +3050,7 @@ function registerExecuteTask(program2) {
|
|
|
3065
3050
|
packageRoot: process.cwd(),
|
|
3066
3051
|
cmdType: "git-commit",
|
|
3067
3052
|
hasUserApproval: true
|
|
3068
|
-
//
|
|
3053
|
+
// apply-immediate default
|
|
3069
3054
|
});
|
|
3070
3055
|
} catch (err2) {
|
|
3071
3056
|
console.warn(
|
|
@@ -3120,7 +3105,7 @@ async function dirSizeKb(dir) {
|
|
|
3120
3105
|
function registerGc(program2) {
|
|
3121
3106
|
program2.command("gc").description(
|
|
3122
3107
|
"Garbage-collect old backup snapshots (immediate by default \u2014 use --dry-run for preview)"
|
|
3123
|
-
).option("--older-than <duration>", "delete snapshots older than (e.g. 30d / 24h / 4w)", "30d").option("--keep-last <N>", "always keep the most recent N snapshots", "0").option("--
|
|
3108
|
+
).option("--older-than <duration>", "delete snapshots older than (e.g. 30d / 24h / 4w)", "30d").option("--keep-last <N>", "always keep the most recent N snapshots", "0").option("--dry-run", "preview only \u2014 do not delete (opt-in for advanced users)").action(async (opts) => {
|
|
3124
3109
|
const dryRun = opts.dryRun === true;
|
|
3125
3110
|
const olderMs = parseDuration(opts.olderThan ?? "30d");
|
|
3126
3111
|
if (olderMs == null) {
|
|
@@ -3379,10 +3364,10 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3379
3364
|
const e = pre.errors[0] ?? err(ctx, "/", "preflight failed", "preflight");
|
|
3380
3365
|
return { ok: false, phase: "preflight", error: e };
|
|
3381
3366
|
}
|
|
3382
|
-
const
|
|
3367
|
+
const settingsPath2 = join(homedir(), ".claude", "settings.json");
|
|
3383
3368
|
let existing;
|
|
3384
3369
|
try {
|
|
3385
|
-
existing = await readFile(
|
|
3370
|
+
existing = await readFile(settingsPath2, "utf8");
|
|
3386
3371
|
} catch {
|
|
3387
3372
|
existing = null;
|
|
3388
3373
|
}
|
|
@@ -3413,7 +3398,7 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3413
3398
|
const newText = `${JSON.stringify(settings, null, 2)}
|
|
3414
3399
|
`;
|
|
3415
3400
|
const plan = {
|
|
3416
|
-
files: [{ target:
|
|
3401
|
+
files: [{ target: settingsPath2, scope: "HOME", oldText: existing ?? "", newText }]
|
|
3417
3402
|
};
|
|
3418
3403
|
process.stdout.write(renderDiff(plan, ctx));
|
|
3419
3404
|
const conf = await confirmAt("L3", { ...ctx});
|
|
@@ -3424,10 +3409,10 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3424
3409
|
if (ctx.opts.dryRun) return { aborted: true, reason: "user-cancel" };
|
|
3425
3410
|
const bk = await backup(plan, ctx);
|
|
3426
3411
|
if (!bk.ok) return { ok: false, phase: "preflight", error: bk.error };
|
|
3427
|
-
await writeFile(
|
|
3412
|
+
await writeFile(settingsPath2, newText);
|
|
3428
3413
|
let verify;
|
|
3429
3414
|
try {
|
|
3430
|
-
verify = JSON.parse(await readFile(
|
|
3415
|
+
verify = JSON.parse(await readFile(settingsPath2, "utf8"));
|
|
3431
3416
|
} catch (e) {
|
|
3432
3417
|
return {
|
|
3433
3418
|
ok: false,
|
|
@@ -3455,7 +3440,7 @@ var installCcHookAdd = async (ctx) => {
|
|
|
3455
3440
|
};
|
|
3456
3441
|
}
|
|
3457
3442
|
await updateInstalled(ctx.cwd, ctx.manifest.metadata.name, "", "");
|
|
3458
|
-
return { ok: true, backupId: bk.backupId, appliedFiles: [
|
|
3443
|
+
return { ok: true, backupId: bk.backupId, appliedFiles: [settingsPath2] };
|
|
3459
3444
|
};
|
|
3460
3445
|
function getUserClaudeJsonPath() {
|
|
3461
3446
|
return join(homedir(), ".claude.json");
|
|
@@ -3491,7 +3476,7 @@ async function isPluginRegistered(pluginName) {
|
|
|
3491
3476
|
return Object.keys(plugins).some((k) => k.split("@")[0] === pluginName);
|
|
3492
3477
|
}
|
|
3493
3478
|
function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
3494
|
-
return new Promise((
|
|
3479
|
+
return new Promise((resolve10) => {
|
|
3495
3480
|
const isWin = process.platform === "win32";
|
|
3496
3481
|
const child = isWin ? spawn("cmd.exe", ["/c", "claude", ...claudeArgs], { cwd, windowsHide: true }) : spawn("claude", claudeArgs, { cwd, shell: false });
|
|
3497
3482
|
let stderr = "";
|
|
@@ -3500,15 +3485,15 @@ function runArgs(claudeArgs, cwd, timeoutMs = 15e3) {
|
|
|
3500
3485
|
});
|
|
3501
3486
|
const timer = setTimeout(() => {
|
|
3502
3487
|
child.kill("SIGKILL");
|
|
3503
|
-
|
|
3488
|
+
resolve10({ exitCode: -1, stderr: `${stderr}[timeout after ${timeoutMs}ms]` });
|
|
3504
3489
|
}, timeoutMs);
|
|
3505
3490
|
child.on("error", (e) => {
|
|
3506
3491
|
clearTimeout(timer);
|
|
3507
|
-
|
|
3492
|
+
resolve10({ exitCode: -1, stderr: `${stderr}${e.message}` });
|
|
3508
3493
|
});
|
|
3509
3494
|
child.on("close", (code) => {
|
|
3510
3495
|
clearTimeout(timer);
|
|
3511
|
-
|
|
3496
|
+
resolve10({ exitCode: code ?? -1, stderr });
|
|
3512
3497
|
});
|
|
3513
3498
|
});
|
|
3514
3499
|
}
|
|
@@ -3687,10 +3672,10 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
3687
3672
|
child.stderr?.setEncoding("utf8").on("data", (chunk) => {
|
|
3688
3673
|
stderr += chunk;
|
|
3689
3674
|
});
|
|
3690
|
-
return await new Promise((
|
|
3675
|
+
return await new Promise((resolve10) => {
|
|
3691
3676
|
const timer = setTimeout(() => {
|
|
3692
3677
|
child.kill("SIGKILL");
|
|
3693
|
-
|
|
3678
|
+
resolve10({
|
|
3694
3679
|
ok: false,
|
|
3695
3680
|
phase: "spawn",
|
|
3696
3681
|
error: {
|
|
@@ -3705,7 +3690,7 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
3705
3690
|
}, effectiveTimeoutMs);
|
|
3706
3691
|
child.on("error", (err2) => {
|
|
3707
3692
|
clearTimeout(timer);
|
|
3708
|
-
|
|
3693
|
+
resolve10({
|
|
3709
3694
|
ok: false,
|
|
3710
3695
|
phase: "spawn",
|
|
3711
3696
|
error: {
|
|
@@ -3720,14 +3705,14 @@ async function spawnCmd(ctx, cmd, args, timeoutMs) {
|
|
|
3720
3705
|
});
|
|
3721
3706
|
child.on("close", (code) => {
|
|
3722
3707
|
clearTimeout(timer);
|
|
3723
|
-
|
|
3708
|
+
resolve10({ ok: true, exitCode: code ?? -1, stdout: stdout2, stderr });
|
|
3724
3709
|
});
|
|
3725
3710
|
});
|
|
3726
3711
|
}
|
|
3727
3712
|
|
|
3728
3713
|
// src/installers/gitCloneWithSetup.ts
|
|
3729
3714
|
function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
3730
|
-
return new Promise((
|
|
3715
|
+
return new Promise((resolve10) => {
|
|
3731
3716
|
const isWin = process.platform === "win32";
|
|
3732
3717
|
const child = isWin ? spawn("cmd.exe", ["/c", "git", "rev-parse", "HEAD"], { cwd, windowsHide: true }) : spawn("git", ["rev-parse", "HEAD"], { cwd, shell: false });
|
|
3733
3718
|
let stdout2 = "";
|
|
@@ -3736,15 +3721,15 @@ function gitRevParseHead(cwd, timeoutMs = 1e4) {
|
|
|
3736
3721
|
});
|
|
3737
3722
|
const timer = setTimeout(() => {
|
|
3738
3723
|
child.kill("SIGKILL");
|
|
3739
|
-
|
|
3724
|
+
resolve10({ sha: "", exit: -1 });
|
|
3740
3725
|
}, timeoutMs);
|
|
3741
3726
|
child.on("error", () => {
|
|
3742
3727
|
clearTimeout(timer);
|
|
3743
|
-
|
|
3728
|
+
resolve10({ sha: "", exit: -1 });
|
|
3744
3729
|
});
|
|
3745
3730
|
child.on("close", (code) => {
|
|
3746
3731
|
clearTimeout(timer);
|
|
3747
|
-
|
|
3732
|
+
resolve10({ sha: stdout2.trim(), exit: code ?? -1 });
|
|
3748
3733
|
});
|
|
3749
3734
|
});
|
|
3750
3735
|
}
|
|
@@ -4475,11 +4460,10 @@ function formatError(e) {
|
|
|
4475
4460
|
return `${head}${where}${tip}`;
|
|
4476
4461
|
}
|
|
4477
4462
|
function registerInstall(program2) {
|
|
4478
|
-
program2.command("install <name>").description("Install an upstream (immediate by default \u2014 use --dry-run for preview)").option("--
|
|
4463
|
+
program2.command("install <name>").description("Install an upstream (immediate by default \u2014 use --dry-run for preview)").option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option("--system", "allow L4 system-wide install (e.g. global npm install)").option("--non-interactive", "skip all prompts (CI / scripts)").option("--full-diff", "expand diffs longer than 200 lines").option("--no-color", "disable ANSI colors (auto-detected when piped)").option(
|
|
4479
4464
|
"--known-good",
|
|
4480
4465
|
"use known-good version lock from versions/<harnessed-ver>-known-good.yaml"
|
|
4481
4466
|
).action(async (name, raw) => {
|
|
4482
|
-
validateNonInteractiveFlags(raw, "install <name>");
|
|
4483
4467
|
const { resolveAlias: resolveAlias2 } = await Promise.resolve().then(() => (init_aliases(), aliases_exports));
|
|
4484
4468
|
const resolvedName = resolveAlias2(name) ?? name;
|
|
4485
4469
|
checkPathSafe(resolvedName);
|
|
@@ -4558,11 +4542,7 @@ async function listBaseManifests(cwd) {
|
|
|
4558
4542
|
function registerInstallBase(program2) {
|
|
4559
4543
|
program2.command("install-base").description(
|
|
4560
4544
|
"Install the phase 1.3 base profile (immediate by default \u2014 use --dry-run for preview)"
|
|
4561
|
-
).option(
|
|
4562
|
-
"--apply",
|
|
4563
|
-
"(deprecated; kept for backward compat \u2014 install-base is immediate by default)"
|
|
4564
|
-
).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").action(async (raw) => {
|
|
4565
|
-
validateNonInteractiveFlags(raw, "install-base");
|
|
4545
|
+
).option("--dry-run", "preview only \u2014 do not write to disk (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts)").action(async (raw) => {
|
|
4566
4546
|
const dryRun = raw.dryRun === true;
|
|
4567
4547
|
const opts = {
|
|
4568
4548
|
apply: !dryRun,
|
|
@@ -4628,11 +4608,7 @@ function basename(upstream) {
|
|
|
4628
4608
|
function registerManifestAdd(program2) {
|
|
4629
4609
|
program2.command("manifest-add <upstream>").description(
|
|
4630
4610
|
"Add a new upstream adapter (EE-5 5-question merge gate; immediate by default \u2014 use --dry-run for preview)"
|
|
4631
|
-
).option("--category <cat>", "manifest category (skill-packs | tools)", "skill-packs").option("--name <name>", "short adapter name (defaults to <upstream> basename)").option(
|
|
4632
|
-
"--apply",
|
|
4633
|
-
"(deprecated; kept for backward compat \u2014 manifest-add persists immediately by default)"
|
|
4634
|
-
).option("--dry-run", "preview only \u2014 do not write JSON (opt-in for advanced users)").option("--non-interactive", "CI/scripts \u2014 requires --apply or --dry-run; WARN-only dry-run").action(async (upstream, raw) => {
|
|
4635
|
-
validateNonInteractiveFlags(raw, "manifest-add <upstream>");
|
|
4611
|
+
).option("--category <cat>", "manifest category (skill-packs | tools)", "skill-packs").option("--name <name>", "short adapter name (defaults to <upstream> basename)").option("--dry-run", "preview only \u2014 do not write JSON (opt-in for advanced users)").option("--non-interactive", "CI/scripts \u2014 WARN-only dry-run").action(async (upstream, raw) => {
|
|
4636
4612
|
const name = raw.name ?? basename(upstream);
|
|
4637
4613
|
const category = raw.category ?? "skill-packs";
|
|
4638
4614
|
const outPath = `manifests/${category}/${name}.ee5-answers.json`;
|
|
@@ -4677,11 +4653,7 @@ function registerManifestAdd(program2) {
|
|
|
4677
4653
|
function registerResearch(program2) {
|
|
4678
4654
|
program2.command("research").description(
|
|
4679
4655
|
"Run research workflow (search category sub-routing \u2192 spawn \u2192 verbatim COMPLETE; immediate by default \u2014 use --dry-run for preview)"
|
|
4680
|
-
).requiredOption("--query <text>", "research prompt (required)").option(
|
|
4681
|
-
"--apply",
|
|
4682
|
-
"(deprecated; kept for backward compat \u2014 research spawns immediately by default)"
|
|
4683
|
-
).option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts) \u2014 requires --apply or --dry-run").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").action(async (raw) => {
|
|
4684
|
-
validateNonInteractiveFlags(raw, "research --query <text>");
|
|
4656
|
+
).requiredOption("--query <text>", "research prompt (required)").option("--dry-run", "preview only \u2014 do not spawn subagent (opt-in for advanced users)").option("--non-interactive", "skip all prompts (CI / scripts)").option("--model <model>", "subagent model: 'haiku' | 'sonnet' | 'opus'").action(async (raw) => {
|
|
4685
4657
|
if (!raw.query) {
|
|
4686
4658
|
console.error("error: --query <text> is required");
|
|
4687
4659
|
process.exit(2);
|
|
@@ -4720,7 +4692,7 @@ function registerResearch(program2) {
|
|
|
4720
4692
|
if ("ok" in result && result.ok === false) {
|
|
4721
4693
|
console.error(`error: ${result.phase} \u2014 ${result.error.message}`);
|
|
4722
4694
|
if (result.phase === "install") {
|
|
4723
|
-
console.error(` fix: 'harnessed install <skill>
|
|
4695
|
+
console.error(` fix: 'harnessed install <skill>' (see error above)`);
|
|
4724
4696
|
}
|
|
4725
4697
|
process.exit(1);
|
|
4726
4698
|
}
|
|
@@ -4810,6 +4782,80 @@ function registerRollback(program2) {
|
|
|
4810
4782
|
console.log(`restored ${meta.files.length} file(s) from ${timestamp}`);
|
|
4811
4783
|
});
|
|
4812
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
|
+
}
|
|
4813
4859
|
init_checkAgentTeams();
|
|
4814
4860
|
var FLAT_LEGACY_DEPRECATED = /* @__PURE__ */ new Set(["plan-feature", "execute-task", "verify-work"]);
|
|
4815
4861
|
var FLAT_LEGACY_KEEP = /* @__PURE__ */ new Set(["research", "retro", "auto"]);
|
|
@@ -5032,6 +5078,18 @@ function registerSetup(program2) {
|
|
|
5032
5078
|
`
|
|
5033
5079
|
Step A complete: ${skillsInstalled} workflow skill(s) installed to ${skillsBase}`
|
|
5034
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
|
+
}
|
|
5035
5093
|
const manifestPaths = await listBaseManifests2(pkgRoot);
|
|
5036
5094
|
const b = await runStepBInstall(manifestPaths);
|
|
5037
5095
|
const stepBMs = (b.elapsedMs / 1e3).toFixed(1);
|
|
@@ -5120,10 +5178,10 @@ var uninstallCcHookAdd = async (ctx) => {
|
|
|
5120
5178
|
}
|
|
5121
5179
|
const abort = dryRunGate(ctx);
|
|
5122
5180
|
if (abort) return abort;
|
|
5123
|
-
const
|
|
5181
|
+
const settingsPath2 = join(homedir(), ".claude", "settings.json");
|
|
5124
5182
|
let existing;
|
|
5125
5183
|
try {
|
|
5126
|
-
existing = await readFile(
|
|
5184
|
+
existing = await readFile(settingsPath2, "utf8");
|
|
5127
5185
|
} catch {
|
|
5128
5186
|
return { ok: true, removedPaths: [] };
|
|
5129
5187
|
}
|
|
@@ -5152,8 +5210,8 @@ var uninstallCcHookAdd = async (ctx) => {
|
|
|
5152
5210
|
if (settings.hooks?.[ev]?.length === before || before === settings.hooks?.[ev]?.length) ;
|
|
5153
5211
|
const newText = `${JSON.stringify(settings, null, 2)}
|
|
5154
5212
|
`;
|
|
5155
|
-
await writeFile(
|
|
5156
|
-
return { ok: true, removedPaths: [
|
|
5213
|
+
await writeFile(settingsPath2, newText);
|
|
5214
|
+
return { ok: true, removedPaths: [settingsPath2] };
|
|
5157
5215
|
};
|
|
5158
5216
|
|
|
5159
5217
|
// src/uninstallers/ccPluginMarketplace.ts
|
|
@@ -5289,7 +5347,7 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
5289
5347
|
const m = install.cmd.match(/npm\s+(?:install|i)\s+(?:-g\s+)?(\S+)/);
|
|
5290
5348
|
const pkg = m?.[1] ?? ctx.manifest.metadata.upstream.source;
|
|
5291
5349
|
const isWin = process.platform === "win32";
|
|
5292
|
-
const result = await new Promise((
|
|
5350
|
+
const result = await new Promise((resolve10) => {
|
|
5293
5351
|
const child = isWin ? spawn("cmd.exe", ["/c", "npm", "uninstall", "-g", pkg], { windowsHide: true }) : spawn("npm", ["uninstall", "-g", pkg], { shell: false });
|
|
5294
5352
|
let stderr = "";
|
|
5295
5353
|
child.stderr?.setEncoding("utf8").on("data", (c) => {
|
|
@@ -5297,15 +5355,15 @@ var uninstallNpmCli = async (ctx) => {
|
|
|
5297
5355
|
});
|
|
5298
5356
|
const timer = setTimeout(() => {
|
|
5299
5357
|
child.kill("SIGKILL");
|
|
5300
|
-
|
|
5358
|
+
resolve10({ exitCode: -1, stderr: `${stderr}[timeout]` });
|
|
5301
5359
|
}, 3e4);
|
|
5302
5360
|
child.on("error", (e) => {
|
|
5303
5361
|
clearTimeout(timer);
|
|
5304
|
-
|
|
5362
|
+
resolve10({ exitCode: -1, stderr: e.message });
|
|
5305
5363
|
});
|
|
5306
5364
|
child.on("close", (code) => {
|
|
5307
5365
|
clearTimeout(timer);
|
|
5308
|
-
|
|
5366
|
+
resolve10({ exitCode: code ?? -1, stderr });
|
|
5309
5367
|
});
|
|
5310
5368
|
});
|
|
5311
5369
|
if (result.exitCode !== 0) {
|
|
@@ -5355,7 +5413,7 @@ async function runUninstall(manifest, opts) {
|
|
|
5355
5413
|
|
|
5356
5414
|
// src/cli/uninstall.ts
|
|
5357
5415
|
function registerUninstall(program2) {
|
|
5358
|
-
program2.command("uninstall <name>").description("Uninstall an upstream (immediate by default \u2014 use --dry-run for preview)").option("--
|
|
5416
|
+
program2.command("uninstall <name>").description("Uninstall an upstream (immediate by default \u2014 use --dry-run for preview)").option("--dry-run", "preview only \u2014 do not delete files (opt-in for advanced users)").option("--yes", "skip interactive confirm (CI / scripts) \u2014 fatal with --dry-run").option("--non-interactive", "alias for --yes (CI compat)").action(async (name, raw) => {
|
|
5359
5417
|
const yes = raw.yes === true || raw.nonInteractive === true;
|
|
5360
5418
|
if (yes && raw.dryRun) {
|
|
5361
5419
|
console.error(
|