harness-bujang 0.8.0 → 0.8.2
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.
|
@@ -170,6 +170,62 @@ If installed via npm, try reinstalling. If running from source, run "npm run bui
|
|
|
170
170
|
);
|
|
171
171
|
}
|
|
172
172
|
var ALL_ADAPTERS = ["cursor", "cline", "aider", "codex", "gemini"];
|
|
173
|
+
var CODEX_BALANCED = {
|
|
174
|
+
director: "gpt-5",
|
|
175
|
+
cofounder: "gpt-5",
|
|
176
|
+
"architect-team": "gpt-5",
|
|
177
|
+
consultant: "gpt-5",
|
|
178
|
+
"security-team": "o1",
|
|
179
|
+
"db-guard-team": "o1",
|
|
180
|
+
"dev-team": "gpt-5-codex",
|
|
181
|
+
"code-review-team": "gpt-5-codex",
|
|
182
|
+
"qa-team": "gpt-4-turbo",
|
|
183
|
+
"verifier-team": "gpt-4-turbo",
|
|
184
|
+
"doc-sync-team": "gpt-4-turbo",
|
|
185
|
+
"research-team": "gpt-5",
|
|
186
|
+
"analysis-team": "gpt-5",
|
|
187
|
+
"script-team": "gpt-4-turbo",
|
|
188
|
+
"image-team": "o1-mini",
|
|
189
|
+
"voice-team": "o1-mini",
|
|
190
|
+
"edit-team": "o1-mini",
|
|
191
|
+
"content-qa-team": "o1-mini"
|
|
192
|
+
};
|
|
193
|
+
var GEMINI_BALANCED = {
|
|
194
|
+
director: "gemini-2.5-pro",
|
|
195
|
+
cofounder: "gemini-2.5-pro",
|
|
196
|
+
"architect-team": "gemini-2.5-pro",
|
|
197
|
+
consultant: "gemini-2.5-pro",
|
|
198
|
+
"security-team": "gemini-2.5-pro",
|
|
199
|
+
"db-guard-team": "gemini-2.5-pro",
|
|
200
|
+
"dev-team": "gemini-2.5-pro",
|
|
201
|
+
"code-review-team": "gemini-2.5-pro",
|
|
202
|
+
"qa-team": "gemini-2.5-flash",
|
|
203
|
+
"verifier-team": "gemini-2.5-flash",
|
|
204
|
+
"doc-sync-team": "gemini-2.5-flash",
|
|
205
|
+
"research-team": "gemini-2.5-pro",
|
|
206
|
+
"analysis-team": "gemini-2.5-pro",
|
|
207
|
+
"script-team": "gemini-2.5-flash",
|
|
208
|
+
"image-team": "gemini-2.5-flash",
|
|
209
|
+
"voice-team": "gemini-2.5-flash",
|
|
210
|
+
"edit-team": "gemini-2.5-flash",
|
|
211
|
+
"content-qa-team": "gemini-2.5-flash"
|
|
212
|
+
};
|
|
213
|
+
function resolveCodexPreset(preset) {
|
|
214
|
+
if (preset === "keep") return {};
|
|
215
|
+
if (preset === "balanced") return { ...CODEX_BALANCED };
|
|
216
|
+
const tier = preset === "cost" ? "gpt-4-turbo" : "gpt-5";
|
|
217
|
+
const out = {};
|
|
218
|
+
for (const k of Object.keys(CODEX_BALANCED)) out[k] = tier;
|
|
219
|
+
return out;
|
|
220
|
+
}
|
|
221
|
+
function resolveGeminiPreset(preset) {
|
|
222
|
+
if (preset === "keep") return {};
|
|
223
|
+
if (preset === "balanced") return { ...GEMINI_BALANCED };
|
|
224
|
+
const tier = preset === "cost" ? "gemini-2.5-flash" : "gemini-2.5-pro";
|
|
225
|
+
const out = {};
|
|
226
|
+
for (const k of Object.keys(GEMINI_BALANCED)) out[k] = tier;
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
173
229
|
var BALANCED_MAPPING = {
|
|
174
230
|
director: "opus",
|
|
175
231
|
cofounder: "opus",
|
|
@@ -233,9 +289,9 @@ async function runInit(args) {
|
|
|
233
289
|
console.log(c.dim(` Language: ${opts.lang}`));
|
|
234
290
|
console.log(c.dim(` Chat backend: ${opts.chatBackend}${opts.chatBackend === "sqlite" ? " (local file)" : " (cloud Postgres)"}`));
|
|
235
291
|
console.log(c.dim(` Tools: claude${opts.adapters.length > 0 ? ` + ${opts.adapters.join(", ")}` : " (only)"}`));
|
|
236
|
-
console.log(c.dim(` Claude
|
|
237
|
-
if (opts.
|
|
238
|
-
if (opts.
|
|
292
|
+
console.log(c.dim(` Claude models: ${describeModelMap(opts.modelMap)}`));
|
|
293
|
+
if (opts.codexModelMap) console.log(c.dim(` Codex models: ${describeAnyMap(opts.codexModelMap)}`));
|
|
294
|
+
if (opts.geminiModelMap) console.log(c.dim(` Gemini models: ${describeAnyMap(opts.geminiModelMap)}`));
|
|
239
295
|
if (opts.aiderModel && opts.aiderModel !== "(skip)") console.log(c.dim(` Aider model: ${opts.aiderModel} (.aider.conf.yml)`));
|
|
240
296
|
if (scan.framework.startsWith("Next.js")) {
|
|
241
297
|
console.log(c.dim(` Chat-room UI: ${opts.installTemplate ? "install" : "skip"}`));
|
|
@@ -396,7 +452,18 @@ async function runInit(args) {
|
|
|
396
452
|
if (opts.chatBackend === "sqlite" && !opts.commitChat) {
|
|
397
453
|
await ensureGitignore(opts.target, [".harness/"]);
|
|
398
454
|
}
|
|
399
|
-
|
|
455
|
+
if (opts.installDeps) {
|
|
456
|
+
await ensurePeerDeps(opts.target, opts.chatBackend);
|
|
457
|
+
} else {
|
|
458
|
+
console.log(c.dim("\u{1F4E6} --no-install-deps \u2014 peer dep auto-install skipped"));
|
|
459
|
+
console.log();
|
|
460
|
+
}
|
|
461
|
+
if (opts.chatBackend === "sqlite") {
|
|
462
|
+
await patchNextConfig(opts.target);
|
|
463
|
+
} else {
|
|
464
|
+
await scaffoldEnvExample(opts.target);
|
|
465
|
+
}
|
|
466
|
+
printBackendInstructions(opts.chatBackend, opts.commitChat, opts.installDeps);
|
|
400
467
|
} else {
|
|
401
468
|
console.log(
|
|
402
469
|
`${c.yellow("\u2139\uFE0E Chat-room UI (Next.js admin route) skipped")} ` + c.dim(`\u2014 your stack is detected as ${scan.framework}.`)
|
|
@@ -419,8 +486,8 @@ async function runInit(args) {
|
|
|
419
486
|
`--target=${opts.target}`,
|
|
420
487
|
"--yes"
|
|
421
488
|
]);
|
|
422
|
-
if (opts.
|
|
423
|
-
if (opts.
|
|
489
|
+
if (opts.codexModelMap && Object.keys(opts.codexModelMap).length > 0) await injectCodexModelMemos(opts.target, opts.codexModelMap);
|
|
490
|
+
if (opts.geminiModelMap && Object.keys(opts.geminiModelMap).length > 0) await injectGeminiModelMemos(opts.target, opts.geminiModelMap);
|
|
424
491
|
if (opts.aiderModel && opts.aiderModel !== "(skip)") await setAiderModel(opts.target, opts.aiderModel);
|
|
425
492
|
}
|
|
426
493
|
console.log(c.bold(c.green("\u2705 Done.")));
|
|
@@ -504,34 +571,35 @@ async function promptInteractive(opts, scan) {
|
|
|
504
571
|
});
|
|
505
572
|
modelMap = preset === "custom" ? await promptCustomModelMap() : resolvePreset(preset);
|
|
506
573
|
}
|
|
507
|
-
let
|
|
574
|
+
let codexModelMap;
|
|
508
575
|
if (adapters.includes("codex")) {
|
|
509
|
-
|
|
510
|
-
message: "\u{1F7E2} Codex \
|
|
576
|
+
const preset = await select({
|
|
577
|
+
message: "\u{1F7E2} Codex \uC5D0\uC774\uC804\uD2B8 \uBAA8\uB378 \uB9E4\uD551? [AGENTS.md \uC758 \uAC01 \uC5D0\uC774\uC804\uD2B8 \uC139\uC158\uC5D0 \uBA54\uBAA8\uB85C \uBC15\uD798 \u2014 \uAC00\uC774\uB4DC\uC6A9]",
|
|
511
578
|
choices: [
|
|
512
|
-
{ name: "gpt-5
|
|
513
|
-
{ name: "
|
|
514
|
-
{ name: "gpt-4-turbo", value: "
|
|
515
|
-
{ name: "
|
|
516
|
-
{ name: "
|
|
517
|
-
{ name: "skip (\uBA54\uBAA8 \uC548 \uBC15\uC74C)", value: "skip" }
|
|
579
|
+
{ name: "balanced \u2014 gpt-5 / gpt-5-codex / o1 / gpt-4-turbo \uC5ED\uD560\uBCC4 \uB9E4\uD551 (\uCD94\uCC9C)", value: "balanced" },
|
|
580
|
+
{ name: "keep \u2014 \uBA54\uBAA8 \uC548 \uBC15\uC74C (\uC0AC\uC6A9\uC790\uAC00 \uCF54\uB371\uC2A4 \uC548\uC5D0\uC11C \uD53D)", value: "keep" },
|
|
581
|
+
{ name: "cost \u2014 \uC804\uBD80 gpt-4-turbo (\uAC00\uC7A5 \uC800\uB834)", value: "cost" },
|
|
582
|
+
{ name: "quality \u2014 \uC804\uBD80 gpt-5 (\uAC00\uC7A5 \uB611\uB611)", value: "quality" },
|
|
583
|
+
{ name: "custom \u2014 \uC5D0\uC774\uC804\uD2B8\uBCC4 \uC9C1\uC811 \uC120\uD0DD (18\uAC1C prompt)", value: "custom" }
|
|
518
584
|
],
|
|
519
|
-
default: "
|
|
585
|
+
default: "balanced"
|
|
520
586
|
});
|
|
587
|
+
codexModelMap = preset === "custom" ? await promptCustomCodexMap() : resolveCodexPreset(preset);
|
|
521
588
|
}
|
|
522
|
-
let
|
|
589
|
+
let geminiModelMap;
|
|
523
590
|
if (adapters.includes("gemini")) {
|
|
524
|
-
|
|
525
|
-
message: "\u{1F535} Gemini \
|
|
591
|
+
const preset = await select({
|
|
592
|
+
message: "\u{1F535} Gemini \uC5D0\uC774\uC804\uD2B8 \uBAA8\uB378 \uB9E4\uD551? [GEMINI.md \uC758 \uAC01 \uC5D0\uC774\uC804\uD2B8 \uC139\uC158\uC5D0 \uBA54\uBAA8\uB85C \uBC15\uD798 \u2014 \uAC00\uC774\uB4DC\uC6A9]",
|
|
526
593
|
choices: [
|
|
527
|
-
{ name: "
|
|
528
|
-
{ name: "
|
|
529
|
-
{ name: "gemini-2.
|
|
530
|
-
{ name: "gemini-2.
|
|
531
|
-
{ name: "
|
|
594
|
+
{ name: "balanced \u2014 pro / flash \uC5ED\uD560\uBCC4 \uB9E4\uD551 (\uCD94\uCC9C)", value: "balanced" },
|
|
595
|
+
{ name: "keep \u2014 \uBA54\uBAA8 \uC548 \uBC15\uC74C (Gemini \uB3C4\uAD6C \uC548\uC5D0\uC11C \uD53D)", value: "keep" },
|
|
596
|
+
{ name: "cost \u2014 \uC804\uBD80 gemini-2.5-flash (\uAC00\uC7A5 \uBE60\uB974\uACE0 \uC800\uB834)", value: "cost" },
|
|
597
|
+
{ name: "quality \u2014 \uC804\uBD80 gemini-2.5-pro (\uAC00\uC7A5 \uB611\uB611)", value: "quality" },
|
|
598
|
+
{ name: "custom \u2014 \uC5D0\uC774\uC804\uD2B8\uBCC4 \uC9C1\uC811 \uC120\uD0DD (18\uAC1C prompt)", value: "custom" }
|
|
532
599
|
],
|
|
533
|
-
default: "
|
|
600
|
+
default: "balanced"
|
|
534
601
|
});
|
|
602
|
+
geminiModelMap = preset === "custom" ? await promptCustomGeminiMap() : resolveGeminiPreset(preset);
|
|
535
603
|
}
|
|
536
604
|
let aiderModel;
|
|
537
605
|
if (adapters.includes("aider")) {
|
|
@@ -561,11 +629,52 @@ async function promptInteractive(opts, scan) {
|
|
|
561
629
|
installTemplate,
|
|
562
630
|
adapters,
|
|
563
631
|
modelMap,
|
|
564
|
-
|
|
565
|
-
|
|
632
|
+
codexModelMap,
|
|
633
|
+
geminiModelMap,
|
|
566
634
|
aiderModel
|
|
567
635
|
};
|
|
568
636
|
}
|
|
637
|
+
async function promptCustomCodexMap() {
|
|
638
|
+
const out = {};
|
|
639
|
+
const slugs = Object.keys(CODEX_BALANCED);
|
|
640
|
+
console.log();
|
|
641
|
+
console.log(c.dim(` Codex custom \uB9E4\uD551 \u2014 ${slugs.length}\uAC1C \uC5D0\uC774\uC804\uD2B8\uB9C8\uB2E4 \uBAA8\uB378\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.`));
|
|
642
|
+
for (const slug of slugs) {
|
|
643
|
+
const tier = await select({
|
|
644
|
+
message: `${slug.padEnd(20)}`,
|
|
645
|
+
choices: [
|
|
646
|
+
{ name: "gpt-5 (\uCD5C\uC2E0, \uD070 \uACB0\uC815)", value: "gpt-5" },
|
|
647
|
+
{ name: "gpt-5-codex (\uCF54\uB529 \uD2B9\uD654)", value: "gpt-5-codex" },
|
|
648
|
+
{ name: "gpt-4-turbo (\uADE0\uD615)", value: "gpt-4-turbo" },
|
|
649
|
+
{ name: "o1 (\uCD94\uB860 \uD2B9\uD654)", value: "o1" },
|
|
650
|
+
{ name: "o1-mini (\uAC00\uBCBC\uC6B4, \uBE60\uB984)", value: "o1-mini" }
|
|
651
|
+
],
|
|
652
|
+
default: CODEX_BALANCED[slug] ?? "gpt-4-turbo"
|
|
653
|
+
});
|
|
654
|
+
out[slug] = tier;
|
|
655
|
+
}
|
|
656
|
+
return out;
|
|
657
|
+
}
|
|
658
|
+
async function promptCustomGeminiMap() {
|
|
659
|
+
const out = {};
|
|
660
|
+
const slugs = Object.keys(GEMINI_BALANCED);
|
|
661
|
+
console.log();
|
|
662
|
+
console.log(c.dim(` Gemini custom \uB9E4\uD551 \u2014 ${slugs.length}\uAC1C \uC5D0\uC774\uC804\uD2B8\uB9C8\uB2E4 \uBAA8\uB378\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694.`));
|
|
663
|
+
for (const slug of slugs) {
|
|
664
|
+
const tier = await select({
|
|
665
|
+
message: `${slug.padEnd(20)}`,
|
|
666
|
+
choices: [
|
|
667
|
+
{ name: "gemini-2.5-pro (\uCD5C\uC2E0, \uAC00\uC7A5 \uB611\uB611)", value: "gemini-2.5-pro" },
|
|
668
|
+
{ name: "gemini-2.5-flash (\uBE60\uB974\uACE0 \uC800\uB834)", value: "gemini-2.5-flash" },
|
|
669
|
+
{ name: "gemini-2.0-pro", value: "gemini-2.0-pro" },
|
|
670
|
+
{ name: "gemini-2.0-flash", value: "gemini-2.0-flash" }
|
|
671
|
+
],
|
|
672
|
+
default: GEMINI_BALANCED[slug] ?? "gemini-2.5-flash"
|
|
673
|
+
});
|
|
674
|
+
out[slug] = tier;
|
|
675
|
+
}
|
|
676
|
+
return out;
|
|
677
|
+
}
|
|
569
678
|
async function promptCustomModelMap() {
|
|
570
679
|
const out = {};
|
|
571
680
|
const slugs = Object.keys(BALANCED_MAPPING);
|
|
@@ -619,8 +728,22 @@ function parseArgs(args) {
|
|
|
619
728
|
}
|
|
620
729
|
modelMap = resolvePreset(modelsRaw);
|
|
621
730
|
}
|
|
622
|
-
const
|
|
623
|
-
const
|
|
731
|
+
const codexPresetRaw = getFlag(args, "--codex-models");
|
|
732
|
+
const geminiPresetRaw = getFlag(args, "--gemini-models");
|
|
733
|
+
let codexModelMap;
|
|
734
|
+
let geminiModelMap;
|
|
735
|
+
if (codexPresetRaw) {
|
|
736
|
+
if (!["balanced", "cost", "quality", "keep"].includes(codexPresetRaw)) {
|
|
737
|
+
throw new Error(`--codex-models must be one of: balanced, cost, quality, keep (got "${codexPresetRaw}")`);
|
|
738
|
+
}
|
|
739
|
+
codexModelMap = resolveCodexPreset(codexPresetRaw);
|
|
740
|
+
}
|
|
741
|
+
if (geminiPresetRaw) {
|
|
742
|
+
if (!["balanced", "cost", "quality", "keep"].includes(geminiPresetRaw)) {
|
|
743
|
+
throw new Error(`--gemini-models must be one of: balanced, cost, quality, keep (got "${geminiPresetRaw}")`);
|
|
744
|
+
}
|
|
745
|
+
geminiModelMap = resolveGeminiPreset(geminiPresetRaw);
|
|
746
|
+
}
|
|
624
747
|
const aiderModel = getFlag(args, "--aider-model");
|
|
625
748
|
return {
|
|
626
749
|
lang,
|
|
@@ -632,11 +755,12 @@ function parseArgs(args) {
|
|
|
632
755
|
installTemplate: !args.includes("--no-template"),
|
|
633
756
|
editClaudeMd: !args.includes("--no-claude-md"),
|
|
634
757
|
seedLearningLog: !args.includes("--no-learning-log"),
|
|
758
|
+
installDeps: !args.includes("--no-install-deps"),
|
|
635
759
|
yes: args.includes("--yes") || args.includes("-y"),
|
|
636
760
|
adapters,
|
|
637
761
|
modelMap,
|
|
638
|
-
|
|
639
|
-
|
|
762
|
+
codexModelMap,
|
|
763
|
+
geminiModelMap,
|
|
640
764
|
aiderModel
|
|
641
765
|
};
|
|
642
766
|
}
|
|
@@ -710,21 +834,138 @@ ${toAdd.join("\n")}
|
|
|
710
834
|
`;
|
|
711
835
|
await fs2.writeFile(gitignorePath, existing + block);
|
|
712
836
|
}
|
|
713
|
-
function
|
|
837
|
+
async function ensurePeerDeps(target, backend) {
|
|
838
|
+
const { detectPM, readDeps, installDeps, buildAddCmd } = await import("./pm-DUCX56FQ.js");
|
|
839
|
+
const pm = await detectPM(target);
|
|
840
|
+
const existing = await readDeps(target);
|
|
841
|
+
const wanted = [];
|
|
714
842
|
if (backend === "sqlite") {
|
|
715
|
-
|
|
843
|
+
if (!existing["better-sqlite3"]) wanted.push({ name: "better-sqlite3" });
|
|
844
|
+
if (!existing["@types/better-sqlite3"]) wanted.push({ name: "@types/better-sqlite3", dev: true });
|
|
845
|
+
} else {
|
|
846
|
+
if (!existing["@supabase/supabase-js"]) wanted.push({ name: "@supabase/supabase-js" });
|
|
847
|
+
}
|
|
848
|
+
if (wanted.length === 0) {
|
|
849
|
+
console.log(c.dim(`\u{1F4E6} Peer deps already installed (${backend}) \u2014 skipped`));
|
|
850
|
+
console.log();
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
const summary = wanted.map((d) => d.name + (d.dev ? c.dim(" (dev)") : "")).join(", ");
|
|
854
|
+
console.log(c.bold(`\u{1F4E6} Auto-installing peer deps via ${pm}`));
|
|
855
|
+
console.log(c.dim(` ${summary}`));
|
|
856
|
+
console.log();
|
|
857
|
+
const result = await installDeps(target, pm, wanted);
|
|
858
|
+
if (result.ok) {
|
|
859
|
+
console.log(c.green(` \u2713 installed`));
|
|
860
|
+
} else {
|
|
861
|
+
console.log(c.yellow(` \u26A0 install failed: ${result.err ?? "unknown"}`));
|
|
862
|
+
console.log(c.dim(` \uC218\uB3D9\uC73C\uB85C \uC2E4\uD589\uD574\uC8FC\uC138\uC694 / Run manually:`));
|
|
863
|
+
const prod = wanted.filter((d) => !d.dev).map((d) => d.name);
|
|
864
|
+
const dev = wanted.filter((d) => d.dev).map((d) => d.name);
|
|
865
|
+
if (prod.length > 0) console.log(c.dim(` $ ${buildAddCmd(pm, prod, false)}`));
|
|
866
|
+
if (dev.length > 0) console.log(c.dim(` $ ${buildAddCmd(pm, dev, true)}`));
|
|
867
|
+
}
|
|
868
|
+
console.log();
|
|
869
|
+
}
|
|
870
|
+
async function patchNextConfig(target) {
|
|
871
|
+
const candidates = ["next.config.ts", "next.config.mjs", "next.config.js"];
|
|
872
|
+
let configPath = null;
|
|
873
|
+
for (const cand of candidates) {
|
|
874
|
+
if (await exists2(path2.join(target, cand))) {
|
|
875
|
+
configPath = path2.join(target, cand);
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (!configPath) {
|
|
880
|
+
console.log(c.dim(`\u{1F4DD} next.config.{js,mjs,ts} not found \u2014 serverExternalPackages patch skipped`));
|
|
881
|
+
console.log();
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
const filename = path2.basename(configPath);
|
|
885
|
+
const raw = await fs2.readFile(configPath, "utf8");
|
|
886
|
+
if (/serverExternalPackages\s*:\s*\[[^\]]*['"]better-sqlite3['"]/.test(raw)) {
|
|
887
|
+
console.log(c.dim(`\u{1F4DD} ${filename} \uC774\uBBF8 'better-sqlite3' \uB4F1\uB85D\uB428 \u2014 skipped`));
|
|
888
|
+
console.log();
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (/serverExternalPackages\s*:\s*\[/.test(raw)) {
|
|
892
|
+
const patched = raw.replace(
|
|
893
|
+
/serverExternalPackages\s*:\s*\[/,
|
|
894
|
+
`serverExternalPackages: ['better-sqlite3', `
|
|
895
|
+
);
|
|
896
|
+
await fs2.writeFile(configPath, patched);
|
|
897
|
+
console.log(c.green(` \u2713 ${filename} \u2190 'better-sqlite3' added to serverExternalPackages`));
|
|
898
|
+
console.log();
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const injectPattern = /((?:const\s+\w+(?:\s*:\s*[^=]+)?\s*=\s*|module\.exports\s*=\s*|export\s+default\s*)\{)/;
|
|
902
|
+
if (injectPattern.test(raw)) {
|
|
903
|
+
const patched = raw.replace(
|
|
904
|
+
injectPattern,
|
|
905
|
+
`$1
|
|
906
|
+
serverExternalPackages: ['better-sqlite3'],`
|
|
907
|
+
);
|
|
908
|
+
await fs2.writeFile(configPath, patched);
|
|
909
|
+
console.log(c.green(` \u2713 ${filename} \u2190 injected serverExternalPackages: ['better-sqlite3']`));
|
|
910
|
+
console.log();
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
console.log(c.yellow(`\u{1F4DD} ${filename} \uC790\uB3D9 \uD328\uCE58 \uBABB \uD588\uC2B5\uB2C8\uB2E4 \u2014 \uB2E4\uC74C\uC744 \uCD94\uAC00\uD574\uC8FC\uC138\uC694:`));
|
|
914
|
+
console.log(c.dim(` {`));
|
|
915
|
+
console.log(c.dim(` serverExternalPackages: ['better-sqlite3'],`));
|
|
916
|
+
console.log(c.dim(` // ...rest of config`));
|
|
917
|
+
console.log(c.dim(` }`));
|
|
918
|
+
console.log();
|
|
919
|
+
}
|
|
920
|
+
async function scaffoldEnvExample(target) {
|
|
921
|
+
const fp = path2.join(target, ".env.local.example");
|
|
922
|
+
let raw = "";
|
|
923
|
+
if (await exists2(fp)) raw = await fs2.readFile(fp, "utf8");
|
|
924
|
+
const HEADER = "# --- Harness-Bujang (Supabase mode) ---";
|
|
925
|
+
const KEYS = [
|
|
926
|
+
"HARNESS_DB=supabase",
|
|
927
|
+
"NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co",
|
|
928
|
+
"SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here",
|
|
929
|
+
"HARNESS_WRITE_SECRET=generate-with-openssl-rand-hex-32",
|
|
930
|
+
"SUPER_ADMIN_EMAILS=you@example.com"
|
|
931
|
+
];
|
|
932
|
+
const missing = KEYS.filter((line) => {
|
|
933
|
+
const key = line.split("=")[0];
|
|
934
|
+
return !new RegExp(`^${key}=`, "m").test(raw);
|
|
935
|
+
});
|
|
936
|
+
if (missing.length === 0 && raw.includes(HEADER)) {
|
|
937
|
+
console.log(c.dim(`\u{1F4DD} .env.local.example \uC774\uBBF8 \uBAA8\uB4E0 Supabase \uD0A4 \uC788\uC74C \u2014 skipped`));
|
|
716
938
|
console.log();
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
const sep = raw && !raw.endsWith("\n") ? "\n" : "";
|
|
942
|
+
const block = `${sep}
|
|
943
|
+
${HEADER}
|
|
944
|
+
${missing.join("\n")}
|
|
945
|
+
`;
|
|
946
|
+
await fs2.writeFile(fp, raw + block);
|
|
947
|
+
console.log(c.green(` \u2713 .env.local.example \u2190 Supabase \uD0A4 ${missing.length}\uAC1C \uCD94\uAC00 (placeholders)`));
|
|
948
|
+
console.log(c.dim(` \u2192 \uC2E4\uC81C \uAC12\uC740 Supabase \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uBCF5\uC0AC\uD574\uC11C .env.local \uC5D0 \uB123\uC5B4\uC8FC\uC138\uC694.`));
|
|
949
|
+
console.log();
|
|
950
|
+
}
|
|
951
|
+
function printBackendInstructions(backend, commitChat, installedDeps) {
|
|
952
|
+
let n = 1;
|
|
953
|
+
if (backend === "sqlite") {
|
|
954
|
+
console.log(c.bold(c.cyan("\u{1F4CB} SQLite mode (default) \u2014 next steps:")));
|
|
720
955
|
console.log();
|
|
721
|
-
|
|
956
|
+
if (!installedDeps) {
|
|
957
|
+
console.log(` ${c.bold(`${n++}.`)} Install the SQLite driver in your project:`);
|
|
958
|
+
console.log(` ${c.dim("$")} ${c.bold("npm i better-sqlite3")}`);
|
|
959
|
+
console.log(` ${c.dim("$")} ${c.bold("npm i -D @types/better-sqlite3")}`);
|
|
960
|
+
console.log();
|
|
961
|
+
}
|
|
962
|
+
console.log(` ${c.bold(`${n++}.`)} (optional) Add to ${c.bold(".env.local")}:`);
|
|
722
963
|
console.log(` ${c.dim("HARNESS_DB=sqlite # default \u2014 can be omitted")}`);
|
|
723
964
|
console.log(` ${c.dim("HARNESS_SQLITE_PATH=./.harness/chat.db # default \u2014 can be omitted")}`);
|
|
724
965
|
console.log(` ${c.bold("HARNESS_WRITE_SECRET=<random> # for bot/script writes")}`);
|
|
725
966
|
console.log(` ${c.bold("SUPER_ADMIN_EMAILS=you@example.com # comma-separated")}`);
|
|
726
967
|
console.log();
|
|
727
|
-
console.log(` ${c.bold(
|
|
968
|
+
console.log(` ${c.bold(`${n++}.`)} Run your dev server and visit ${c.bold("/admin/harness")}.`);
|
|
728
969
|
console.log();
|
|
729
970
|
if (commitChat) {
|
|
730
971
|
console.log(c.dim(` \u{1F4E6} ${c.bold("--commit-chat")} mode: .harness/ NOT added to .gitignore.`));
|
|
@@ -738,59 +979,65 @@ function printBackendInstructions(backend, commitChat) {
|
|
|
738
979
|
} else {
|
|
739
980
|
console.log(c.bold(c.cyan("\u{1F4CB} Supabase mode \u2014 next steps:")));
|
|
740
981
|
console.log();
|
|
741
|
-
|
|
982
|
+
if (!installedDeps) {
|
|
983
|
+
console.log(` ${c.bold(`${n++}.`)} Install the Supabase client in your project:`);
|
|
984
|
+
console.log(` ${c.dim("$")} ${c.bold("npm i @supabase/supabase-js")}`);
|
|
985
|
+
console.log();
|
|
986
|
+
}
|
|
987
|
+
console.log(` ${c.bold(`${n++}.`)} Apply the migrations to your Supabase project:`);
|
|
742
988
|
console.log(` ${c.dim("$")} ${c.bold("supabase db push")}`);
|
|
743
989
|
console.log(` ${c.dim(" (or run them manually via psql / SQL editor)")}`);
|
|
744
990
|
console.log();
|
|
745
|
-
console.log(` ${c.bold(
|
|
991
|
+
console.log(` ${c.bold(`${n++}.`)} Fill in the keys in ${c.bold(".env.local")} ${c.dim("(template scaffolded at .env.local.example)")}:`);
|
|
746
992
|
console.log(` ${c.bold("HARNESS_DB=supabase")}`);
|
|
747
993
|
console.log(` ${c.bold("NEXT_PUBLIC_SUPABASE_URL=...")}`);
|
|
748
994
|
console.log(` ${c.bold("SUPABASE_SERVICE_ROLE_KEY=...")}`);
|
|
749
995
|
console.log(` ${c.bold("HARNESS_WRITE_SECRET=<random>")}`);
|
|
750
996
|
console.log(` ${c.bold("SUPER_ADMIN_EMAILS=you@example.com")}`);
|
|
751
997
|
console.log();
|
|
752
|
-
console.log(` ${c.bold(
|
|
998
|
+
console.log(` ${c.bold(`${n++}.`)} Implement ${c.bold("verifySuperAdmin()")} at ${c.bold("@/lib/utils/admin")}`);
|
|
753
999
|
console.log(` (see ${c.dim("packages/template/README.md")} for an example).`);
|
|
754
1000
|
console.log();
|
|
755
|
-
console.log(` ${c.bold(
|
|
1001
|
+
console.log(` ${c.bold(`${n++}.`)} Run your dev server and visit ${c.bold("/admin/harness")}.`);
|
|
756
1002
|
}
|
|
757
1003
|
console.log();
|
|
758
1004
|
}
|
|
759
|
-
async function
|
|
1005
|
+
async function injectCodexModelMemos(target, modelMap) {
|
|
760
1006
|
const fp = path2.join(target, "AGENTS.md");
|
|
761
1007
|
if (!await exists2(fp)) return;
|
|
762
1008
|
const raw = await fs2.readFile(fp, "utf8");
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const lines = raw.split("\n");
|
|
768
|
-
const h1Idx = lines.findIndex((l) => l.startsWith("# "));
|
|
769
|
-
if (h1Idx < 0) {
|
|
770
|
-
await fs2.writeFile(fp, memo + raw);
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
lines.splice(h1Idx + 1, 0, memo);
|
|
774
|
-
await fs2.writeFile(fp, lines.join("\n"));
|
|
775
|
-
console.log(c.dim(` \u2713 AGENTS.md \u2190 Codex \uAD8C\uC7A5 \uBAA8\uB378 \uBA54\uBAA8: ${model}`));
|
|
1009
|
+
const updated = injectPerAgentMemos(raw, modelMap, "\uCF54\uB371\uC2A4 / Copilot \uC548\uC5D0\uC11C \uC774 \uBAA8\uB378\uB85C \uC791\uC5C5");
|
|
1010
|
+
await fs2.writeFile(fp, updated);
|
|
1011
|
+
const count = Object.keys(modelMap).length;
|
|
1012
|
+
console.log(c.dim(` \u2713 AGENTS.md \u2190 Codex \uAD8C\uC7A5 \uBAA8\uB378 \uBA54\uBAA8 ${count}\uAC74 (\uAC01 \uC5D0\uC774\uC804\uD2B8 \uC139\uC158 \uC704)`));
|
|
776
1013
|
}
|
|
777
|
-
async function
|
|
1014
|
+
async function injectGeminiModelMemos(target, modelMap) {
|
|
778
1015
|
const fp = path2.join(target, "GEMINI.md");
|
|
779
1016
|
if (!await exists2(fp)) return;
|
|
780
1017
|
const raw = await fs2.readFile(fp, "utf8");
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1018
|
+
const updated = injectPerAgentMemos(raw, modelMap, "Gemini CLI / Antigravity \uC548\uC5D0\uC11C \uC774 \uBAA8\uB378\uB85C \uC791\uC5C5");
|
|
1019
|
+
await fs2.writeFile(fp, updated);
|
|
1020
|
+
const count = Object.keys(modelMap).length;
|
|
1021
|
+
console.log(c.dim(` \u2713 GEMINI.md \u2190 Gemini \uAD8C\uC7A5 \uBAA8\uB378 \uBA54\uBAA8 ${count}\uAC74 (\uAC01 \uC5D0\uC774\uC804\uD2B8 \uC139\uC158 \uC704)`));
|
|
1022
|
+
}
|
|
1023
|
+
function injectPerAgentMemos(raw, modelMap, hint) {
|
|
785
1024
|
const lines = raw.split("\n");
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
1025
|
+
const out = [];
|
|
1026
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1027
|
+
const line = lines[i];
|
|
1028
|
+
const m = /^##\s+([a-z][a-z0-9-]*)\s*$/.exec(line);
|
|
1029
|
+
if (m) {
|
|
1030
|
+
const slug = m[1];
|
|
1031
|
+
const model = modelMap[slug];
|
|
1032
|
+
const prev = out[out.length - 1] ?? "";
|
|
1033
|
+
if (model && !prev.includes("\u{1F4A1} Recommended")) {
|
|
1034
|
+
out.push(`> \u{1F4A1} Recommended model: \`${model}\` \u2014 ${hint}`);
|
|
1035
|
+
out.push("");
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
out.push(line);
|
|
790
1039
|
}
|
|
791
|
-
|
|
792
|
-
await fs2.writeFile(fp, lines.join("\n"));
|
|
793
|
-
console.log(c.dim(` \u2713 GEMINI.md \u2190 Gemini \uAD8C\uC7A5 \uBAA8\uB378 \uBA54\uBAA8: ${model}`));
|
|
1040
|
+
return out.join("\n");
|
|
794
1041
|
}
|
|
795
1042
|
async function setAiderModel(target, model) {
|
|
796
1043
|
const fp = path2.join(target, ".aider.conf.yml");
|
|
@@ -827,6 +1074,13 @@ function describeModelMap(map) {
|
|
|
827
1074
|
}
|
|
828
1075
|
return parts.join(" \xB7 ");
|
|
829
1076
|
}
|
|
1077
|
+
function describeAnyMap(map) {
|
|
1078
|
+
if (Object.keys(map).length === 0) return "keep (\uBA54\uBAA8 \uC548 \uBC15\uC74C)";
|
|
1079
|
+
const counts = {};
|
|
1080
|
+
for (const v of Object.values(map)) counts[v] = (counts[v] ?? 0) + 1;
|
|
1081
|
+
const parts = Object.entries(counts).sort((a, b) => b[1] - a[1]).map(([model, n]) => `${n} ${model}`);
|
|
1082
|
+
return parts.join(" \xB7 ");
|
|
1083
|
+
}
|
|
830
1084
|
function stackReviewRules(framework) {
|
|
831
1085
|
if (framework.startsWith("Next.js")) {
|
|
832
1086
|
return `Next.js App Router rules:
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,7 @@ ${c.bold("init \uC635\uC158:")}
|
|
|
48
48
|
--no-template \uD1A1\uBC29 UI \uC124\uCE58 \uAC74\uB108\uB6F0\uAE30
|
|
49
49
|
--no-claude-md CLAUDE.md \uC218\uC815 \uAC74\uB108\uB6F0\uAE30
|
|
50
50
|
--no-learning-log \uD559\uC2B5 \uB85C\uADF8 \uC2DC\uB4DC \uAC74\uB108\uB6F0\uAE30
|
|
51
|
+
--no-install-deps peer dep \uC790\uB3D9 \uC124\uCE58 \uAC74\uB108\uB6F0\uAE30 (better-sqlite3 / @supabase/supabase-js)
|
|
51
52
|
--yes, -y \uD504\uB86C\uD504\uD2B8 \uAC74\uB108\uB6F0\uACE0 \uB36E\uC5B4\uC4F0\uAE30 (CI / \uC2A4\uD06C\uB9BD\uD2B8\uC6A9)
|
|
52
53
|
|
|
53
54
|
${c.dim("--yes \uC548 \uBD99\uC774\uBA74 \uC778\uD130\uB799\uD2F0\uBE0C \uC14B\uC5C5 (\uC5B8\uC5B4 / \uBC31\uC5D4\uB4DC / \uB3C4\uAD6C / \uBAA8\uB378 \uD504\uB9AC\uC14B prompt).")}
|
|
@@ -130,6 +131,7 @@ ${c.bold("Options for init:")}
|
|
|
130
131
|
--no-template Skip chat-room UI install
|
|
131
132
|
--no-claude-md Skip CLAUDE.md edit
|
|
132
133
|
--no-learning-log Skip learning log seed
|
|
134
|
+
--no-install-deps Skip auto-install of peer deps (better-sqlite3 / @supabase/supabase-js)
|
|
133
135
|
--yes, -y Skip prompts and overwrite (non-interactive \u2014 for CI / scripts)
|
|
134
136
|
|
|
135
137
|
${c.dim("Run without --yes for an interactive setup (prompts for language, backend, etc.).")}
|
|
@@ -189,7 +191,7 @@ async function main() {
|
|
|
189
191
|
const command = args[0];
|
|
190
192
|
switch (command) {
|
|
191
193
|
case "init":
|
|
192
|
-
await (await import("./init-
|
|
194
|
+
await (await import("./init-GKZGQDJM.js")).runInit(args.slice(1));
|
|
193
195
|
break;
|
|
194
196
|
case "status":
|
|
195
197
|
await (await import("./status-UE2TQQPU.js")).runStatus(args.slice(1));
|
|
@@ -201,7 +203,7 @@ async function main() {
|
|
|
201
203
|
await (await import("./adapt-VPWOYF6W.js")).runAdapt(args.slice(1));
|
|
202
204
|
break;
|
|
203
205
|
case "update":
|
|
204
|
-
await (await import("./update-
|
|
206
|
+
await (await import("./update-KPWC4YNN.js")).runUpdate(args.slice(1));
|
|
205
207
|
break;
|
|
206
208
|
case "migrate":
|
|
207
209
|
await (await import("./migrate-PISZFX6C.js")).runMigrate(args.slice(1));
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/pm.ts
|
|
2
|
+
import * as fs from "fs/promises";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
async function exists(p) {
|
|
6
|
+
try {
|
|
7
|
+
await fs.access(p);
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async function detectPM(target) {
|
|
14
|
+
try {
|
|
15
|
+
const raw = await fs.readFile(path.join(target, "package.json"), "utf8");
|
|
16
|
+
const pkg = JSON.parse(raw);
|
|
17
|
+
const pmField = pkg.packageManager ?? "";
|
|
18
|
+
if (pmField.startsWith("pnpm")) return "pnpm";
|
|
19
|
+
if (pmField.startsWith("yarn")) return "yarn";
|
|
20
|
+
if (pmField.startsWith("bun")) return "bun";
|
|
21
|
+
if (pmField.startsWith("npm")) return "npm";
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
if (await exists(path.join(target, "pnpm-lock.yaml"))) return "pnpm";
|
|
25
|
+
if (await exists(path.join(target, "yarn.lock"))) return "yarn";
|
|
26
|
+
if (await exists(path.join(target, "bun.lockb"))) return "bun";
|
|
27
|
+
if (await exists(path.join(target, "bun.lock"))) return "bun";
|
|
28
|
+
if (await exists(path.join(target, "package-lock.json"))) return "npm";
|
|
29
|
+
return "npm";
|
|
30
|
+
}
|
|
31
|
+
async function readDeps(target) {
|
|
32
|
+
try {
|
|
33
|
+
const raw = await fs.readFile(path.join(target, "package.json"), "utf8");
|
|
34
|
+
const pkg = JSON.parse(raw);
|
|
35
|
+
return {
|
|
36
|
+
...pkg.dependencies ?? {},
|
|
37
|
+
...pkg.devDependencies ?? {},
|
|
38
|
+
...pkg.peerDependencies ?? {}
|
|
39
|
+
};
|
|
40
|
+
} catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function installDeps(target, pm, deps) {
|
|
45
|
+
if (deps.length === 0) return { ok: true, cmd: "(no deps to install)" };
|
|
46
|
+
const prod = deps.filter((d) => !d.dev).map((d) => d.name);
|
|
47
|
+
const dev = deps.filter((d) => d.dev).map((d) => d.name);
|
|
48
|
+
const cmds = [];
|
|
49
|
+
if (prod.length > 0) cmds.push(buildAddCmd(pm, prod, false));
|
|
50
|
+
if (dev.length > 0) cmds.push(buildAddCmd(pm, dev, true));
|
|
51
|
+
for (const cmd of cmds) {
|
|
52
|
+
const result = await runCmd(cmd, target);
|
|
53
|
+
if (!result.ok) return { ok: false, cmd, err: result.err };
|
|
54
|
+
}
|
|
55
|
+
return { ok: true, cmd: cmds.join(" && ") };
|
|
56
|
+
}
|
|
57
|
+
function buildAddCmd(pm, names, dev) {
|
|
58
|
+
const list = names.join(" ");
|
|
59
|
+
switch (pm) {
|
|
60
|
+
case "pnpm":
|
|
61
|
+
return dev ? `pnpm add -D ${list}` : `pnpm add ${list}`;
|
|
62
|
+
case "yarn":
|
|
63
|
+
return dev ? `yarn add -D ${list}` : `yarn add ${list}`;
|
|
64
|
+
case "bun":
|
|
65
|
+
return dev ? `bun add -d ${list}` : `bun add ${list}`;
|
|
66
|
+
case "npm":
|
|
67
|
+
return dev ? `npm i -D ${list}` : `npm i ${list}`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function runCmd(cmd, cwd) {
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
const parts = cmd.split(" ");
|
|
73
|
+
const bin = parts[0];
|
|
74
|
+
const args = parts.slice(1);
|
|
75
|
+
const proc = spawn(bin, args, {
|
|
76
|
+
cwd,
|
|
77
|
+
stdio: "inherit",
|
|
78
|
+
// shell:true on Windows so .cmd shims (npm.cmd, pnpm.cmd) resolve correctly.
|
|
79
|
+
shell: process.platform === "win32"
|
|
80
|
+
});
|
|
81
|
+
proc.on("error", (err) => resolve({ ok: false, err: err.message }));
|
|
82
|
+
proc.on("close", (code) => resolve({
|
|
83
|
+
ok: code === 0,
|
|
84
|
+
err: code === 0 ? void 0 : `exit code ${code}`
|
|
85
|
+
}));
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
buildAddCmd,
|
|
90
|
+
detectPM,
|
|
91
|
+
installDeps,
|
|
92
|
+
readDeps
|
|
93
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-bujang",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
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",
|