harness-bujang 0.5.10 → 0.6.0

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.
@@ -6,7 +6,7 @@ import {
6
6
  import * as fs2 from "fs/promises";
7
7
  import * as path2 from "path";
8
8
  import { fileURLToPath } from "url";
9
- import { select, confirm } from "@inquirer/prompts";
9
+ import { select, confirm, checkbox } from "@inquirer/prompts";
10
10
 
11
11
  // src/scan.ts
12
12
  import * as fs from "fs/promises";
@@ -169,6 +169,27 @@ async function resolveAssetPaths() {
169
169
  If installed via npm, try reinstalling. If running from source, run "npm run build" in packages/cli first.`
170
170
  );
171
171
  }
172
+ var ALL_ADAPTERS = ["cursor", "cline", "aider", "codex", "gemini"];
173
+ var BALANCED_MAPPING = {
174
+ director: "opus",
175
+ cofounder: "opus",
176
+ "architect-team": "opus",
177
+ consultant: "opus",
178
+ "security-team": "opus",
179
+ "db-guard-team": "opus",
180
+ "dev-team": "sonnet",
181
+ "code-review-team": "sonnet",
182
+ "qa-team": "sonnet",
183
+ "doc-sync-team": "sonnet",
184
+ "research-team": "sonnet",
185
+ "analysis-team": "sonnet",
186
+ "script-team": "sonnet",
187
+ "verifier-team": "haiku",
188
+ "image-team": "haiku",
189
+ "voice-team": "haiku",
190
+ "edit-team": "haiku",
191
+ "content-qa-team": "haiku"
192
+ };
172
193
  async function runInit(args) {
173
194
  let opts = parseArgs(args);
174
195
  const assets = await resolveAssetPaths();
@@ -211,6 +232,8 @@ async function runInit(args) {
211
232
  console.log(c.bold("\u{1F4CB} Configuration"));
212
233
  console.log(c.dim(` Language: ${opts.lang}`));
213
234
  console.log(c.dim(` Chat backend: ${opts.chatBackend}${opts.chatBackend === "sqlite" ? " (local file)" : " (cloud Postgres)"}`));
235
+ console.log(c.dim(` Tools: claude${opts.adapters.length > 0 ? ` + ${opts.adapters.join(", ")}` : " (only)"}`));
236
+ console.log(c.dim(` Models: ${describeModelMap(opts.modelMap)}`));
214
237
  if (scan.framework.startsWith("Next.js")) {
215
238
  console.log(c.dim(` Chat-room UI: ${opts.installTemplate ? "install" : "skip"}`));
216
239
  }
@@ -287,8 +310,12 @@ async function runInit(args) {
287
310
  continue;
288
311
  }
289
312
  const raw = await fs2.readFile(path2.join(agentsSrc, f), "utf8");
290
- await fs2.writeFile(dst, renderTemplate(raw, context));
291
- console.log(` ${c.green("\u2713")} ${f}`);
313
+ const slug = f.replace(/\.md$/, "");
314
+ const override = opts.modelMap[slug];
315
+ const rendered = renderTemplate(raw, context);
316
+ const final = override ? overrideModelFrontmatter(rendered, override) : rendered;
317
+ await fs2.writeFile(dst, final);
318
+ console.log(` ${c.green("\u2713")} ${f}${override ? c.dim(` \u2192 model: ${override}`) : ""}`);
292
319
  }
293
320
  console.log();
294
321
  if (opts.editClaudeMd) {
@@ -380,6 +407,16 @@ async function runInit(args) {
380
407
  console.log();
381
408
  }
382
409
  }
410
+ if (opts.adapters.length > 0) {
411
+ console.log(c.bold("\u{1F501} Fanning out to extra tool adapters"));
412
+ console.log(c.dim(` Targets: ${opts.adapters.join(", ")}`));
413
+ const { runAdapt } = await import("./adapt-VPWOYF6W.js");
414
+ await runAdapt([
415
+ `--to=${opts.adapters.join(",")}`,
416
+ `--target=${opts.target}`,
417
+ "--yes"
418
+ ]);
419
+ }
383
420
  console.log(c.bold(c.green("\u2705 Done.")));
384
421
  console.log();
385
422
  console.log("Next steps:");
@@ -431,6 +468,35 @@ async function promptInteractive(opts, scan) {
431
468
  ],
432
469
  default: opts.chatBackend
433
470
  });
471
+ const isPreset = (t) => opts.adapters.includes(t);
472
+ const adapters = await checkbox({
473
+ message: "Extra tool adapters? (Claude Code is always installed at .claude/agents/ \u2014 these add files for OTHER tools)",
474
+ choices: [
475
+ { name: "Cursor (.cursor/rules/bujang-*.mdc)", value: "cursor", checked: isPreset("cursor") },
476
+ { name: "Codex / Copilot (AGENTS.md)", value: "codex", checked: isPreset("codex") },
477
+ { name: "Cline (.clinerules/bujang-*.md)", value: "cline", checked: isPreset("cline") },
478
+ { name: "Aider (CONVENTIONS.md + .aider.conf.yml)", value: "aider", checked: isPreset("aider") },
479
+ { name: "Gemini / Antigravity (GEMINI.md + .gemini/styleguide.md)", value: "gemini", checked: isPreset("gemini") }
480
+ ],
481
+ required: false
482
+ });
483
+ const preset = await select({
484
+ message: "Per-agent Claude model? (only affects .claude/agents/ \u2014 other tools manage models themselves)",
485
+ choices: [
486
+ { name: "balanced \u2014 opus / sonnet / haiku mix (recommended, ~60% cost cut)", value: "balanced" },
487
+ { name: "keep \u2014 leave each agent's default model untouched", value: "keep" },
488
+ { name: "cost \u2014 all haiku (cheapest, fastest)", value: "cost" },
489
+ { name: "quality \u2014 all opus (most expensive, highest quality)", value: "quality" },
490
+ { name: "custom \u2014 pick model per agent (18 prompts)", value: "custom" }
491
+ ],
492
+ default: "balanced"
493
+ });
494
+ let modelMap = {};
495
+ if (preset === "custom") {
496
+ modelMap = await promptCustomModelMap();
497
+ } else {
498
+ modelMap = resolvePreset(preset);
499
+ }
434
500
  let installTemplate = opts.installTemplate;
435
501
  if (scan.framework.startsWith("Next.js") && opts.installTemplate) {
436
502
  installTemplate = await confirm({
@@ -438,7 +504,26 @@ async function promptInteractive(opts, scan) {
438
504
  default: true
439
505
  });
440
506
  }
441
- return { ...opts, lang, chatBackend, installTemplate };
507
+ return { ...opts, lang, chatBackend, installTemplate, adapters, modelMap };
508
+ }
509
+ async function promptCustomModelMap() {
510
+ const out = {};
511
+ const slugs = Object.keys(BALANCED_MAPPING);
512
+ console.log();
513
+ console.log(c.dim(` Custom mapping \u2014 pick a model for each of ${slugs.length} agents.`));
514
+ for (const slug of slugs) {
515
+ const tier = await select({
516
+ message: `${slug.padEnd(20)}`,
517
+ choices: [
518
+ { name: "opus (heaviest, smartest)", value: "opus" },
519
+ { name: "sonnet (balanced)", value: "sonnet" },
520
+ { name: "haiku (lightest, fastest, cheap)", value: "haiku" }
521
+ ],
522
+ default: BALANCED_MAPPING[slug] ?? "sonnet"
523
+ });
524
+ out[slug] = tier;
525
+ }
526
+ return out;
442
527
  }
443
528
  function parseArgs(args) {
444
529
  const lang = getFlag(args, "--lang") ?? "ko";
@@ -450,6 +535,30 @@ function parseArgs(args) {
450
535
  throw new Error(`--chat must be "sqlite" or "supabase", got "${chatBackend}"`);
451
536
  }
452
537
  const targetRaw = getFlag(args, "--target") ?? ".";
538
+ const toolsRaw = getFlag(args, "--tools");
539
+ let adapters = [];
540
+ if (toolsRaw) {
541
+ const parts = toolsRaw === "all" ? ALL_ADAPTERS.slice() : toolsRaw.split(",").map((t) => t.trim()).filter(Boolean);
542
+ for (const t of parts) {
543
+ if (t === "claude" || t === "claude-code") continue;
544
+ if (!ALL_ADAPTERS.includes(t)) {
545
+ throw new Error(
546
+ `Unknown tool "${t}" in --tools. Allowed: claude, ${ALL_ADAPTERS.join(", ")}, all`
547
+ );
548
+ }
549
+ if (!adapters.includes(t)) adapters.push(t);
550
+ }
551
+ }
552
+ const modelsRaw = getFlag(args, "--models");
553
+ let modelMap = {};
554
+ if (modelsRaw) {
555
+ if (!["balanced", "cost", "quality", "keep"].includes(modelsRaw)) {
556
+ throw new Error(
557
+ `--models must be one of: balanced, cost, quality, keep (got "${modelsRaw}")`
558
+ );
559
+ }
560
+ modelMap = resolvePreset(modelsRaw);
561
+ }
453
562
  return {
454
563
  lang,
455
564
  target: path2.resolve(targetRaw),
@@ -460,9 +569,19 @@ function parseArgs(args) {
460
569
  installTemplate: !args.includes("--no-template"),
461
570
  editClaudeMd: !args.includes("--no-claude-md"),
462
571
  seedLearningLog: !args.includes("--no-learning-log"),
463
- yes: args.includes("--yes") || args.includes("-y")
572
+ yes: args.includes("--yes") || args.includes("-y"),
573
+ adapters,
574
+ modelMap
464
575
  };
465
576
  }
577
+ function resolvePreset(preset) {
578
+ if (preset === "keep") return {};
579
+ if (preset === "balanced") return { ...BALANCED_MAPPING };
580
+ const tier = preset === "cost" ? "haiku" : "opus";
581
+ const out = {};
582
+ for (const k of Object.keys(BALANCED_MAPPING)) out[k] = tier;
583
+ return out;
584
+ }
466
585
  function getFlag(args, name) {
467
586
  for (const a of args) {
468
587
  if (a.startsWith(`${name}=`)) return a.slice(name.length + 1);
@@ -571,6 +690,26 @@ function printBackendInstructions(backend, commitChat) {
571
690
  }
572
691
  console.log();
573
692
  }
693
+ function overrideModelFrontmatter(content, model) {
694
+ if (!content.startsWith("---\n")) return content;
695
+ const end = content.indexOf("\n---\n", 4);
696
+ if (end < 0) return content;
697
+ const fmRaw = content.slice(0, end);
698
+ const rest = content.slice(end);
699
+ const newFm = /^model:\s*\S+/m.test(fmRaw) ? fmRaw.replace(/^model:\s*\S+/m, `model: ${model}`) : fmRaw + `
700
+ model: ${model}`;
701
+ return newFm + rest;
702
+ }
703
+ function describeModelMap(map) {
704
+ if (Object.keys(map).length === 0) return "keep (use each agent's default)";
705
+ const counts = { opus: 0, sonnet: 0, haiku: 0 };
706
+ for (const v of Object.values(map)) counts[v]++;
707
+ const parts = [];
708
+ for (const tier of ["opus", "sonnet", "haiku"]) {
709
+ if (counts[tier] > 0) parts.push(`${counts[tier]} ${tier}`);
710
+ }
711
+ return parts.join(" \xB7 ");
712
+ }
574
713
  function stackReviewRules(framework) {
575
714
  if (framework.startsWith("Next.js")) {
576
715
  return `Next.js App Router rules:
package/dist/index.js CHANGED
@@ -1,6 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import * as fs from "fs/promises";
5
+ import * as path from "path";
6
+ import { fileURLToPath } from "url";
7
+ var __filename = fileURLToPath(import.meta.url);
8
+ var __dirname = path.dirname(__filename);
9
+ async function readVersion() {
10
+ try {
11
+ const pkgRaw = await fs.readFile(path.resolve(__dirname, "..", "package.json"), "utf8");
12
+ return JSON.parse(pkgRaw).version ?? "unknown";
13
+ } catch {
14
+ return "unknown";
15
+ }
16
+ }
4
17
  var c = {
5
18
  bold: (s) => `\x1B[1m${s}\x1B[22m`,
6
19
  dim: (s) => `\x1B[2m${s}\x1B[22m`,
@@ -25,6 +38,10 @@ ${c.bold("Options for init:")}
25
38
  --lang=<ko|en> Agent language (default: ko \u2014 full \uBD80\uC7A5 persona)
26
39
  --chat=<sqlite|supabase> Chat-room backend (default: sqlite \u2014 local file, no setup)
27
40
  --commit-chat Don't gitignore .harness/ (for solo cross-machine sync via git)
41
+ --tools=<list> Extra tool adapters: cursor,cline,aider,codex,gemini,all
42
+ (Claude Code is always installed at .claude/agents/)
43
+ --models=<preset> Per-agent Claude model preset: balanced (recommended),
44
+ keep (default), cost (all haiku), quality (all opus)
28
45
  --target=<path> Project root (default: cwd)
29
46
  --framework=<name> Override detected framework
30
47
  --db=<name> Override detected project DB (separate from --chat)
@@ -90,7 +107,7 @@ async function main() {
90
107
  const command = args[0];
91
108
  switch (command) {
92
109
  case "init":
93
- await (await import("./init-S3YF6RT4.js")).runInit(args.slice(1));
110
+ await (await import("./init-4G7R63DX.js")).runInit(args.slice(1));
94
111
  break;
95
112
  case "status":
96
113
  await (await import("./status-UE2TQQPU.js")).runStatus(args.slice(1));
@@ -102,14 +119,14 @@ async function main() {
102
119
  await (await import("./adapt-VPWOYF6W.js")).runAdapt(args.slice(1));
103
120
  break;
104
121
  case "update":
105
- await (await import("./update-4WS42LXD.js")).runUpdate(args.slice(1));
122
+ await (await import("./update-KHDRQZQE.js")).runUpdate(args.slice(1));
106
123
  break;
107
124
  case "migrate":
108
125
  await (await import("./migrate-PISZFX6C.js")).runMigrate(args.slice(1));
109
126
  break;
110
127
  case "--version":
111
128
  case "-v":
112
- console.log("0.5.9");
129
+ console.log(await readVersion());
113
130
  break;
114
131
  case "--help":
115
132
  case "-h":
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  printRestartReminder,
3
3
  runInit
4
- } from "./chunk-FE2GLGVF.js";
4
+ } from "./chunk-6LROBVZM.js";
5
5
  import "./chunk-7DAHO2GN.js";
6
6
  export {
7
7
  printRestartReminder,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  printRestartReminder,
3
3
  scanProject
4
- } from "./chunk-FE2GLGVF.js";
4
+ } from "./chunk-6LROBVZM.js";
5
5
  import {
6
6
  renderTemplate
7
7
  } from "./chunk-7DAHO2GN.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-bujang",
3
- "version": "0.5.10",
3
+ "version": "0.6.0",
4
4
  "description": "Install the Harness-Bujang multi-agent harness into any project — Director, 7 specialist teams, real-time chat-room UI. Korean and English personas. Works with Claude Code, Cursor, Cline, Aider, or any tool that reads .claude/agents/.",
5
5
  "keywords": [
6
6
  "claude-code",