harness-bujang 0.5.3 β 0.5.5
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
CHANGED
|
@@ -36,22 +36,69 @@ npx harness-bujang update
|
|
|
36
36
|
> you've added to existing agent files. `CLAUDE.md` and
|
|
37
37
|
> `docs/AGENT_LEARNING_LOG.md` are never touched by any of the three commands.
|
|
38
38
|
|
|
39
|
-
###
|
|
39
|
+
### π¬ Open the chat-room β two ways (natural language recommended)
|
|
40
40
|
|
|
41
|
+
After install, just **say it in plain language** inside Claude Code β the Director will spawn the viewer in the background:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
"Director, open the chat room"
|
|
45
|
+
"λΆμ₯λ ν‘λ°© μ΄μ΄μ£ΌμΈμ"
|
|
46
|
+
"open chat" / "show the chat"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
β The Director auto-runs `npx harness-bujang chat` (background) β browser opens β http://localhost:7777 KakaoTalk-style room.
|
|
50
|
+
|
|
51
|
+
To close, also natural language:
|
|
52
|
+
```
|
|
53
|
+
"close the chat room"
|
|
54
|
+
"ν‘λ°© λ«μμ€"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Manual command (works on any stack β Next.js, Rails, Django, Express, β¦):
|
|
41
58
|
```bash
|
|
42
|
-
# Standalone viewer (works on Next.js, Rails, Django, Express, β¦) β no setup
|
|
43
59
|
npx harness-bujang chat
|
|
44
|
-
# β opens http://localhost:7777 in your browser
|
|
45
60
|
```
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
project that uses the SQLite chat backend (the default). For projects that have
|
|
49
|
-
not posted any messages yet, pass `--create` to bootstrap an empty DB and seed:
|
|
62
|
+
β Reads `.harness/chat.db` directly. From 0.5.3, the DB is auto-created on first run if missing.
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
### π¬ How to use it β call by name
|
|
65
|
+
|
|
66
|
+
After install, the harness runs **inside Claude Code** by addressing personas:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
"Director, please add a refund API"
|
|
70
|
+
"λΆμ₯λ, κ²°μ νλΆ API λ§λ€μ΄μ£ΌμΈμ"
|
|
71
|
+
β Director persona
|
|
72
|
+
ββ Pre-confirm: "Plan to invoke dev-team + security-team + db-guard. Proceed?"
|
|
73
|
+
ββ Principal OK β parallel team dispatch (per mapping table)
|
|
74
|
+
ββ Each step β chat-room INSERT (live in viewer)
|
|
75
|
+
ββ Consolidated principal report
|
|
76
|
+
|
|
77
|
+
"Cofounder, is our BM viable?"
|
|
78
|
+
"곡λλν, μ°λ¦¬ BM μ΄λλ‘ κ°λ λ κΉ?"
|
|
79
|
+
β Cofounder persona (peer, not subordinate)
|
|
80
|
+
ββ Equal debate + push-back
|
|
81
|
+
ββ Calls consultant / research-team / analysis-team if data needed
|
|
82
|
+
ββ Pushes the decision
|
|
53
83
|
```
|
|
54
84
|
|
|
85
|
+
**Calling rules**:
|
|
86
|
+
|
|
87
|
+
| Trigger | What happens |
|
|
88
|
+
|---------|--------------|
|
|
89
|
+
| **"Director, ..."** / "λΆμ₯λ, ..." | Director persona β pre-confirm, mapping, chat INSERT, consolidated report |
|
|
90
|
+
| **"Cofounder, ..."** / "곡λλν, ..." | Cofounder persona β peer debate / strategy / decision push |
|
|
91
|
+
| **plain "..."** (no name) | Plain Claude β knows harness rules but skips the full workflow |
|
|
92
|
+
| **"dev-team, ..."** directly | β Won't work β the 16 teams are dispatched **by** Director / Cofounder, not addressed directly by the principal |
|
|
93
|
+
|
|
94
|
+
> π‘ **For the full workflow (pre-confirm + mapping + chat + consolidated report), name the persona.** For quick one-offs, plain Claude is fine.
|
|
95
|
+
|
|
96
|
+
Natural-language triggers:
|
|
97
|
+
- Open chat: `"λΆμ₯λ ν‘λ°© μ΄μ΄μ£ΌμΈμ"` / `"open the chat room"`
|
|
98
|
+
- Code review: `"Director, please review the PR"` / `"λΆμ₯λ PR 리뷰 λΆνλ립λλ€"`
|
|
99
|
+
- Onboard team: `"Director, hire a marketing team"` / `"λΆμ₯λ λ§μΌν
ν μ±μ©ν΄μ£ΌμΈμ"`
|
|
100
|
+
- Strategy: `"Cofounder, what do you think?"` / `"곡λλν μ견 μ’"`
|
|
101
|
+
|
|
55
102
|
## What it does
|
|
56
103
|
|
|
57
104
|
1. **Scans** the project β framework (Next.js / SvelteKit / Astro / Rails / Django / β¦), language, DB (Supabase / Prisma / Drizzle / TypeORM), UI lib, payment integration, GitHub user.
|
package/dist/index.js
CHANGED
|
@@ -400,9 +400,30 @@ async function runInit(args) {
|
|
|
400
400
|
if (scan.framework.startsWith("Next.js") && opts.installTemplate) {
|
|
401
401
|
console.log(` ${c.cyan("3.")} Watch ${c.bold(context.ADMIN_HARNESS_ROUTE)} for live updates (after env setup)`);
|
|
402
402
|
} else {
|
|
403
|
-
console.log(` ${c.cyan("3.")} Watch the chat room: ${c.bold("npx harness-bujang chat
|
|
403
|
+
console.log(` ${c.cyan("3.")} Watch the chat room: ${c.bold("npx harness-bujang chat")} ${c.dim("\u2192 http://localhost:7777")}`);
|
|
404
404
|
}
|
|
405
405
|
console.log();
|
|
406
|
+
printRestartReminder(opts.lang);
|
|
407
|
+
}
|
|
408
|
+
function printRestartReminder(lang) {
|
|
409
|
+
const ko = lang === "ko";
|
|
410
|
+
const line = (s) => ` ${c.dim("\u2502")} ${s}`;
|
|
411
|
+
const top = ` ${c.dim("\u256D" + "\u2500".repeat(64) + "\u256E")}`;
|
|
412
|
+
const bot = ` ${c.dim("\u2570" + "\u2500".repeat(64) + "\u256F")}`;
|
|
413
|
+
console.log(top);
|
|
414
|
+
console.log(line(ko ? c.bold(c.yellow("\u26A0\uFE0F Claude Code \uAC00 \uC774\uBBF8 \uB5A0 \uC788\uB2E4\uBA74 \uC5D0\uC774\uC804\uD2B8 \uC7AC\uB85C\uB4DC \uD544\uC694!")) : c.bold(c.yellow("\u26A0\uFE0F Claude Code already running? Reload agents!"))));
|
|
415
|
+
console.log(line(""));
|
|
416
|
+
console.log(line(ko ? "\uC5D0\uC774\uC804\uD2B8 \uB4F1\uB85D\uC740 \uC138\uC158 \uC2DC\uC791 \uC2DC\uC810\uC5D0\uB9CC \uC77C\uC5B4\uB098\uBBC0\uB85C \uC0C8\uB85C \uAE50 \uD300\uC774" : "Agents are registered at session start only, so the new teams"));
|
|
417
|
+
console.log(line(ko ? "\uBC14\uB85C \uC778\uC2DD\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uB2E4\uC74C \uC911 \uD55C \uAC00\uC9C0\uB97C \uD574\uC8FC\uC138\uC694:" : "won't be visible yet. Pick one:"));
|
|
418
|
+
console.log(line(""));
|
|
419
|
+
console.log(line(` ${c.green("A.")} ${c.bold(ko ? "Claude Code \uC548\uC5D0\uC11C: " : "Inside Claude Code: ")}${c.cyan("/agents")}`));
|
|
420
|
+
console.log(line(` ${c.dim(ko ? "\u2192 \uC5D0\uC774\uC804\uD2B8 \uBA54\uB274\uC5D0\uC11C \uC7AC\uB85C\uB4DC" : "\u2192 open the agent menu / refresh")}`));
|
|
421
|
+
console.log(line(""));
|
|
422
|
+
console.log(line(` ${c.green("B.")} ${c.bold(ko ? "\uC548\uB418\uBA74 Claude Code \uC644\uC804 \uC885\uB8CC \u2192 \uAC19\uC740 \uD3F4\uB354\uC5D0\uC11C \uB2E4\uC2DC \uC2DC\uC791" : "Or fully restart Claude Code in this folder")}`));
|
|
423
|
+
console.log(line(""));
|
|
424
|
+
console.log(line(c.dim(ko ? "/clear \uB294 \uCEE8\uD14D\uC2A4\uD2B8\uB9CC \uBE44\uC6C1\uB2C8\uB2E4 \u2014 \uC5D0\uC774\uC804\uD2B8 \uC7AC\uB4F1\uB85D \uC548 \uB428" : "/clear only wipes context \u2014 it does NOT re-register agents")));
|
|
425
|
+
console.log(bot);
|
|
426
|
+
console.log();
|
|
406
427
|
}
|
|
407
428
|
async function promptInteractive(opts, scan) {
|
|
408
429
|
const lang = await select({
|
|
@@ -1323,7 +1344,7 @@ function render() {
|
|
|
1323
1344
|
html += '</div>';
|
|
1324
1345
|
html += '</div>';
|
|
1325
1346
|
|
|
1326
|
-
html += '<div class="flex-1 overflow-y-auto">';
|
|
1347
|
+
html += '<div id="room-list" class="flex-1 overflow-y-auto">';
|
|
1327
1348
|
// When 'unread' filter is active, only show rooms with unread > 0.
|
|
1328
1349
|
const visibleRooms = state.filter === 'unread'
|
|
1329
1350
|
? ROOMS.filter((r) => unreadByRoom[r.id] > 0)
|
|
@@ -1410,8 +1431,27 @@ function render() {
|
|
|
1410
1431
|
}
|
|
1411
1432
|
html += '</div>';
|
|
1412
1433
|
|
|
1434
|
+
// Save scroll positions BEFORE replacing innerHTML \u2014 otherwise every 2s poll
|
|
1435
|
+
// resets the sidebar room-list scroll to top, and the conversation re-snaps
|
|
1436
|
+
// to the bottom even if the user was reading older messages.
|
|
1437
|
+
const oldRoomList = document.getElementById('room-list');
|
|
1438
|
+
const oldConv = document.getElementById('conversation');
|
|
1439
|
+
const savedRoomScroll = oldRoomList ? oldRoomList.scrollTop : 0;
|
|
1440
|
+
let savedConvScroll = null;
|
|
1441
|
+
let convWasAtBottom = true;
|
|
1442
|
+
if (oldConv) {
|
|
1443
|
+
savedConvScroll = oldConv.scrollTop;
|
|
1444
|
+
// "At bottom" within ~80px \u2014 if user was at bottom we keep them at bottom
|
|
1445
|
+
// (so new messages stay visible). If they scrolled up, preserve position.
|
|
1446
|
+
convWasAtBottom = (oldConv.scrollHeight - oldConv.scrollTop - oldConv.clientHeight) < 80;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1413
1449
|
root.innerHTML = html;
|
|
1414
1450
|
|
|
1451
|
+
// Restore scroll positions.
|
|
1452
|
+
const newRoomList = document.getElementById('room-list');
|
|
1453
|
+
if (newRoomList) newRoomList.scrollTop = savedRoomScroll;
|
|
1454
|
+
|
|
1415
1455
|
// Re-bind filter buttons (\uC804\uCCB4 / \uC548\uC77D\uC74C).
|
|
1416
1456
|
document.querySelectorAll('[data-filter]').forEach((el) => {
|
|
1417
1457
|
el.addEventListener('click', () => {
|
|
@@ -1434,11 +1474,20 @@ function render() {
|
|
|
1434
1474
|
});
|
|
1435
1475
|
});
|
|
1436
1476
|
|
|
1437
|
-
//
|
|
1477
|
+
// Conversation scroll handling:
|
|
1478
|
+
// - First render of a room \u2192 scroll to bottom (latest msg visible)
|
|
1479
|
+
// - User was at bottom \u2192 keep at new bottom (so new msgs auto-show)
|
|
1480
|
+
// - User scrolled up to read history \u2192 preserve their position
|
|
1438
1481
|
const conv = document.getElementById('conversation');
|
|
1439
|
-
if (conv
|
|
1440
|
-
conv.
|
|
1441
|
-
|
|
1482
|
+
if (conv) {
|
|
1483
|
+
if (conv.dataset.scrolled !== state.selectedRoom) {
|
|
1484
|
+
conv.scrollTop = conv.scrollHeight;
|
|
1485
|
+
conv.dataset.scrolled = state.selectedRoom;
|
|
1486
|
+
} else if (convWasAtBottom) {
|
|
1487
|
+
conv.scrollTop = conv.scrollHeight;
|
|
1488
|
+
} else if (savedConvScroll !== null) {
|
|
1489
|
+
conv.scrollTop = savedConvScroll;
|
|
1490
|
+
}
|
|
1442
1491
|
}
|
|
1443
1492
|
}
|
|
1444
1493
|
|
|
@@ -1937,6 +1986,7 @@ async function runUpdate(args) {
|
|
|
1937
1986
|
console.log(" To overwrite everything (e.g. for a clean reset), use instead:");
|
|
1938
1987
|
console.log(` ${c6.cyan("npx harness-bujang init --yes")}`);
|
|
1939
1988
|
console.log();
|
|
1989
|
+
printRestartReminder(opts.lang);
|
|
1940
1990
|
}
|
|
1941
1991
|
async function resolveAssetPaths2() {
|
|
1942
1992
|
const packaged = path7.resolve(__dirname3, "..", "templates");
|
|
@@ -2149,7 +2199,7 @@ async function main() {
|
|
|
2149
2199
|
break;
|
|
2150
2200
|
case "--version":
|
|
2151
2201
|
case "-v":
|
|
2152
|
-
console.log("0.5.
|
|
2202
|
+
console.log("0.5.5");
|
|
2153
2203
|
break;
|
|
2154
2204
|
case "--help":
|
|
2155
2205
|
case "-h":
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "harness-bujang",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
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",
|
|
@@ -28,7 +28,7 @@ export async function GET(request: NextRequest) {
|
|
|
28
28
|
const before = searchParams.get('before') ?? undefined;
|
|
29
29
|
const limit = parseInt(searchParams.get('limit') ?? '50', 10);
|
|
30
30
|
|
|
31
|
-
const db = getHarnessDb();
|
|
31
|
+
const db = await getHarnessDb();
|
|
32
32
|
const messages = before ? await db.list({ before, limit }) : await db.list({ days });
|
|
33
33
|
|
|
34
34
|
return NextResponse.json({ data: messages });
|
|
@@ -81,7 +81,7 @@ export async function POST(request: NextRequest) {
|
|
|
81
81
|
from = trimmed;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const db = getHarnessDb();
|
|
84
|
+
const db = await getHarnessDb();
|
|
85
85
|
|
|
86
86
|
let msgId = id;
|
|
87
87
|
if (!msgId) {
|
|
@@ -22,7 +22,7 @@ export async function POST(request: NextRequest) {
|
|
|
22
22
|
return NextResponse.json({ error: 'message is required' }, { status: 400 });
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const db = getHarnessDb();
|
|
25
|
+
const db = await getHarnessDb();
|
|
26
26
|
const total = await db.count();
|
|
27
27
|
|
|
28
28
|
const msg: ChatMessage = {
|
|
@@ -5,20 +5,30 @@
|
|
|
5
5
|
// - "supabase" β cloud Postgres via Supabase, prod-ready
|
|
6
6
|
//
|
|
7
7
|
// To switch later, run `bujang migrate --to=supabase` (or back to sqlite).
|
|
8
|
+
//
|
|
9
|
+
// IMPORTANT: adapters are loaded with **dynamic `import()`** so that a project
|
|
10
|
+
// using only the SQLite backend doesn't pay for `@supabase/supabase-js` at
|
|
11
|
+
// bundle / install time, and vice versa. (Static imports here would force
|
|
12
|
+
// Next.js / webpack / turbopack to compile both adapters into every chunk
|
|
13
|
+
// that touches the DB β discovered in nextjs-e2e-test against a sqlite-only
|
|
14
|
+
// install where supabase-js wasn't installed and the build failed.)
|
|
8
15
|
|
|
9
16
|
import type { HarnessDb, HarnessDbMode } from './types';
|
|
10
|
-
import { createSqliteAdapter } from './sqlite';
|
|
11
|
-
import { createSupabaseAdapter } from './supabase';
|
|
12
17
|
|
|
13
18
|
export type { HarnessDb, ChatMessage, ListOptions, HarnessDbMode } from './types';
|
|
14
|
-
export { createSqliteAdapter, createSupabaseAdapter };
|
|
15
19
|
|
|
16
20
|
let _instance: HarnessDb | null = null;
|
|
21
|
+
let _pending: Promise<HarnessDb> | null = null;
|
|
17
22
|
|
|
18
|
-
export function getHarnessDb(): HarnessDb {
|
|
19
|
-
if (_instance) return _instance;
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
export function getHarnessDb(): Promise<HarnessDb> {
|
|
24
|
+
if (_instance) return Promise.resolve(_instance);
|
|
25
|
+
if (_pending) return _pending;
|
|
26
|
+
_pending = createAdapter(currentMode()).then((db) => {
|
|
27
|
+
_instance = db;
|
|
28
|
+
_pending = null;
|
|
29
|
+
return db;
|
|
30
|
+
});
|
|
31
|
+
return _pending;
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
export function currentMode(): HarnessDbMode {
|
|
@@ -29,11 +39,13 @@ export function currentMode(): HarnessDbMode {
|
|
|
29
39
|
);
|
|
30
40
|
}
|
|
31
41
|
|
|
32
|
-
export function createAdapter(mode: HarnessDbMode): HarnessDb {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
export async function createAdapter(mode: HarnessDbMode): Promise<HarnessDb> {
|
|
43
|
+
if (mode === 'sqlite') {
|
|
44
|
+
const { createSqliteAdapter } = await import('./sqlite');
|
|
45
|
+
return createSqliteAdapter();
|
|
36
46
|
}
|
|
47
|
+
const { createSupabaseAdapter } = await import('./supabase');
|
|
48
|
+
return createSupabaseAdapter();
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
/**
|
|
@@ -42,4 +54,5 @@ export function createAdapter(mode: HarnessDbMode): HarnessDb {
|
|
|
42
54
|
*/
|
|
43
55
|
export function resetHarnessDb(): void {
|
|
44
56
|
_instance = null;
|
|
57
|
+
_pending = null;
|
|
45
58
|
}
|