@elyun/bylane 1.15.0 → 1.16.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.
@@ -19,14 +19,11 @@ Claude가 직접 실행하지 않는다. 사용자에게 아래 명령을 안내
19
19
  모니터 대시보드는 터미널에서 직접 실행하세요:
20
20
 
21
21
  ```bash
22
- npm run monitor
22
+ npx @elyun/bylane monitor
23
23
  ```
24
24
 
25
- byLane이 설치된 디렉토리에서 실행하거나, 현재 프로젝트에서:
26
-
27
- ```bash
28
- npm run monitor --prefix ~/.claude/bylane
29
- ```
25
+ > `npx`를 사용하면 항상 최신 버전 모니터가 실행됩니다.
26
+ > 로컬 node_modules의 구버전이 실행되지 않습니다.
30
27
 
31
28
  **종료**: `q` 또는 `Ctrl+C`
32
29
 
@@ -63,7 +63,7 @@ import('./src/state.js').then(({readState, writeState}) => {
63
63
  "
64
64
  ```
65
65
 
66
- 4. 다음 pending 항목으로 반복. pending 없으면 30초 대기 후 재확인.
66
+ 4. 다음 pending 항목으로 반복. pending 없으면 5분 대기 후 재확인 (폴러 주기와 동일).
67
67
 
68
68
  ## 큐 항목 스키마
69
69
 
@@ -74,7 +74,7 @@ import('./src/state.js').then(({readState, writeState}) => {
74
74
  ```
75
75
 
76
76
  3. 다음 pending 항목으로 반복
77
- 4. pending 없으면 30초 대기 후 재확인
77
+ 4. pending 없으면 5분 대기 후 재확인 (폴러 주기와 동일)
78
78
 
79
79
  ## 큐 항목 스키마
80
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elyun/bylane",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Frontend development harness for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -12,6 +12,11 @@ const args = process.argv.slice(2)
12
12
  const command = args[0] || 'install'
13
13
  const useSymlink = args.includes('--symlink')
14
14
 
15
+ // 사용자 설정 파일 — 절대 덮어쓰지 않는다
16
+ const USER_CONFIG_FILES = [
17
+ '.bylane/bylane.json',
18
+ ]
19
+
15
20
  const TARGETS = [
16
21
  { src: join(ROOT, 'commands'), dest: join(CLAUDE_DIR, 'commands'), label: 'Commands' },
17
22
  { src: join(ROOT, 'hooks'), dest: join(CLAUDE_DIR, 'hooks'), label: 'Hooks' },
@@ -31,7 +36,7 @@ function backupAndCopy(src, dest, file, label) {
31
36
  const backupPath = `${destFile}.bak`
32
37
  renameSync(destFile, backupPath)
33
38
  copyFileSync(srcFile, destFile)
34
- console.log(` ~ ${label}: ${file} (기존 파일 -> ${file}.bak)`)
39
+ console.log(` ~ ${label}: ${file} (업데이트됨, 기존 파일 -> ${file}.bak)`)
35
40
  } else {
36
41
  copyFileSync(srcFile, destFile)
37
42
  console.log(` + ${label}: ${file}`)
@@ -48,41 +53,41 @@ function registerHooks() {
48
53
  const hookScript = join(CLAUDE_DIR, 'hooks', 'bylane-agent-tracker.js')
49
54
  settings.hooks = settings.hooks ?? {}
50
55
 
51
- // PreToolUse
52
- settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? []
53
- const preExists = settings.hooks.PreToolUse.some(h =>
54
- h.hooks?.some(hh => hh.command?.includes('bylane-agent-tracker'))
55
- )
56
- if (!preExists) {
57
- settings.hooks.PreToolUse.push({
58
- matcher: 'Agent',
59
- hooks: [{ type: 'command', command: `node ${hookScript} pre` }]
60
- })
61
- console.log(' + Hook: PreToolUse/Agent → bylane-agent-tracker')
62
- } else {
63
- console.log(' = Hook: PreToolUse/Agent (이미 등록됨)')
64
- }
56
+ // 기존 bylane 훅 제거 후 재등록 (버전 업 시 경로 변경 대응)
57
+ const stripBylane = (arr) =>
58
+ (arr ?? []).filter(h => !h.hooks?.some(hh => hh.command?.includes('bylane-agent-tracker')))
65
59
 
66
- // PostToolUse
67
- settings.hooks.PostToolUse = settings.hooks.PostToolUse ?? []
68
- const postExists = settings.hooks.PostToolUse.some(h =>
69
- h.hooks?.some(hh => hh.command?.includes('bylane-agent-tracker'))
70
- )
71
- if (!postExists) {
72
- settings.hooks.PostToolUse.push({
73
- matcher: 'Agent',
74
- hooks: [{ type: 'command', command: `node ${hookScript} post` }]
75
- })
76
- console.log(' + Hook: PostToolUse/Agent → bylane-agent-tracker')
77
- } else {
78
- console.log(' = Hook: PostToolUse/Agent (이미 등록됨)')
79
- }
60
+ settings.hooks.PreToolUse = [
61
+ ...stripBylane(settings.hooks.PreToolUse),
62
+ { matcher: 'Agent', hooks: [{ type: 'command', command: `node ${hookScript} pre` }] }
63
+ ]
64
+ settings.hooks.PostToolUse = [
65
+ ...stripBylane(settings.hooks.PostToolUse),
66
+ { matcher: 'Agent', hooks: [{ type: 'command', command: `node ${hookScript} post` }] }
67
+ ]
80
68
 
81
69
  writeFileSync(settingsPath, JSON.stringify(settings, null, 2))
70
+ console.log(' ~ Hook: bylane-agent-tracker 등록 (최신 경로로 갱신)')
71
+ }
72
+
73
+ function preservedConfigs() {
74
+ const found = USER_CONFIG_FILES.filter(f => existsSync(f))
75
+ if (found.length > 0) {
76
+ console.log('\n [보존된 사용자 설정]')
77
+ found.forEach(f => console.log(` * ${f}`))
78
+ }
79
+ }
80
+
81
+ function isUpdateMode() {
82
+ return existsSync(join(CLAUDE_DIR, 'commands', 'bylane.md'))
82
83
  }
83
84
 
84
85
  function install() {
85
- console.log('\n byLane 설치 중...\n')
86
+ const updating = isUpdateMode()
87
+ console.log(updating
88
+ ? '\n byLane 업데이트 중...\n'
89
+ : '\n byLane 설치 중...\n'
90
+ )
86
91
 
87
92
  for (const { src, dest, label } of TARGETS) {
88
93
  mkdirSync(dest, { recursive: true })
@@ -108,8 +113,17 @@ function install() {
108
113
 
109
114
  console.log('')
110
115
  registerHooks()
116
+ preservedConfigs()
111
117
 
112
- console.log(`
118
+ if (updating) {
119
+ console.log(`
120
+ byLane 업데이트 완료!
121
+
122
+ 사용자 설정(.bylane/bylane.json)은 그대로 유지됩니다.
123
+ Claude Code를 재시작하면 변경사항이 적용됩니다.
124
+ `)
125
+ } else {
126
+ console.log(`
113
127
  byLane 설치 완료!
114
128
 
115
129
  다음 단계:
@@ -122,12 +136,19 @@ function install() {
122
136
 
123
137
  /bylane 다크모드 토글 추가해줘
124
138
  `)
139
+ }
125
140
  }
126
141
 
127
142
  if (command === 'install') {
128
143
  install()
144
+ } else if (command === 'monitor') {
145
+ // 항상 현재 패키지의 모니터 실행 (버전 일치 보장)
146
+ const monitorPath = join(__dirname, 'monitor', 'index.js')
147
+ const { spawn } = await import('child_process')
148
+ const child = spawn(process.execPath, [monitorPath], { stdio: 'inherit' })
149
+ child.on('exit', code => process.exit(code ?? 0))
129
150
  } else {
130
151
  console.error(`알 수 없는 명령: ${command}`)
131
- console.error('사용법: npx bylane [install] [--symlink]')
152
+ console.error('사용법: npx @elyun/bylane [install|monitor] [--symlink]')
132
153
  process.exit(1)
133
154
  }
@@ -9,9 +9,48 @@ export function createLayout() {
9
9
  const screen = blessed.screen({
10
10
  smartCSR: true,
11
11
  fullUnicode: true,
12
+ dockBorders: true,
12
13
  title: 'byLane Monitor'
13
14
  })
14
15
 
16
+ // 최소 크기 경고 오버레이
17
+ const tooSmall = blessed.box({
18
+ top: 'center',
19
+ left: 'center',
20
+ width: 40,
21
+ height: 5,
22
+ content: '\n 터미널이 너무 작습니다.\n 최소 80×24 이상으로 키워주세요.',
23
+ align: 'center',
24
+ tags: true,
25
+ border: { type: 'line' },
26
+ style: { fg: 'white', bg: 'red', border: { fg: 'white' } },
27
+ hidden: true
28
+ })
29
+ screen.append(tooSmall)
30
+
31
+ function checkSize() {
32
+ const cols = screen.width
33
+ const rows = screen.height
34
+ if (cols < 80 || rows < 24) {
35
+ tooSmall.show()
36
+ } else {
37
+ tooSmall.hide()
38
+ }
39
+ screen.render()
40
+ }
41
+
42
+ screen.on('resize', () => {
43
+ // 약간의 지연으로 tmux/pane 리사이즈 완료 후 렌더링
44
+ setTimeout(() => {
45
+ screen.alloc()
46
+ screen.render()
47
+ checkSize()
48
+ }, 50)
49
+ })
50
+
51
+ // 초기 크기 체크
52
+ process.nextTick(checkSize)
53
+
15
54
  const header = createHeader(screen)
16
55
  const pipeline = createPipelinePanel(screen)
17
56
  const log = createLogPanel(screen)