eojeon-cli 0.3.0 → 0.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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  All notable changes to this project are documented here. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioning: [SemVer](https://semver.org/).
4
4
 
5
+ ## [0.3.1] - 2026-10-25
6
+
7
+ ### Fixed
8
+ - `eojeon claude` no longer gives up when the court is busy (another claude already running in it) or not started yet: it keeps trying every 3 s and attaches as soon as the court is free, then makes claude redraw so the web shows the full screen.
9
+
5
10
  ## [0.3.0] - 2026-10-25
6
11
 
7
12
  ### Added
@@ -43,6 +48,7 @@ First public version.
43
48
  - Hook payloads trimmed and secrets masked before leaving the hook process.
44
49
  - `scripts/check-publish.js` blocks publishing local paths, e-mail addresses or token-shaped strings.
45
50
 
51
+ [0.3.1]: https://github.com/hiio420pr/eojeon/releases/tag/v0.3.1
46
52
  [0.3.0]: https://github.com/hiio420pr/eojeon/releases/tag/v0.3.0
47
53
  [0.2.0]: https://github.com/hiio420pr/eojeon/releases/tag/v0.2.0
48
54
  [0.1.0]: https://github.com/hiio420pr/eojeon/releases/tag/v0.1.0
package/README.md CHANGED
@@ -123,7 +123,7 @@ npx eojeon-cli claude --continue # 다른 터미널에서 claude 대신 이렇
123
123
 
124
124
  - 웹 하단 터미널은 그 터미널의 화면을 그대로 보여 줍니다(크기도 그 터미널을 따름). 웹에서 입력하거나 윤허·불허 도장을 눌러도 그 claude 로 들어갑니다.
125
125
  - 어전이 꺼져 있으면 그냥 평범한 claude 로 실행됩니다.
126
- - 한 번에 하나만 연결됩니다. 어전 안에서 입궐한 claude 가 있으면 먼저 퇴청하세요.
126
+ - 한 번에 하나만 연결됩니다. 어전 안에서 입궐한 claude 가 있으면 화면에서 **퇴청**하세요. 그러면 몇 초 안에 이 터미널이 자동으로 붙습니다(어전을 나중에 켜도 마찬가지).
127
127
 
128
128
  ### 이미 쓰던 Claude Code 보기 (`eojeon watch`)
129
129
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eojeon-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Claude Code as a Joseon royal court: issue a decree, AI officials (subagents) work, and ask for your royal assent. Pixel-art throne hall around your real Claude Code CLI. Unofficial. · Claude Code를 어전회의로 — 교지를 내리면 AI 신하들이 일하고 윤허를 청한다. 비공식 도구.",
5
5
  "keywords": [
6
6
  "claude-code",
package/server/attach.js CHANGED
@@ -56,33 +56,56 @@ export async function run(args) {
56
56
 
57
57
  const rt = readRuntime();
58
58
  let ws = null;
59
- if (rt) {
60
- const r = await connect(rt, path.basename(cwd), cols, rows);
59
+ let pty = null;
60
+ const send = (m) => ws && ws.readyState === 1 && ws.send(JSON.stringify(m));
61
+
62
+ // (re)connect to the court; when it is busy or off, keep trying quietly so the mirror starts as soon as it can
63
+ let connecting = false;
64
+ async function tryAttach() {
65
+ if (ws || connecting) return;
66
+ const now = readRuntime();
67
+ if (!now) return;
68
+ connecting = true;
69
+ const r = await connect(now, path.basename(cwd), cols, rows);
70
+ connecting = false;
71
+ if (!r.ws) return;
61
72
  ws = r.ws;
62
- if (!ws) console.error(`어전에 연결하지 못했습니다 (${r.why}). 그냥 claude 로 실행합니다.`);
73
+ ws.on('message', (raw) => {
74
+ let m;
75
+ try { m = JSON.parse(raw.toString()); } catch { return; }
76
+ if (m.t === 'input' && typeof m.d === 'string' && pty) pty.write(m.d);
77
+ });
78
+ ws.on('close', () => {
79
+ ws = null;
80
+ });
81
+ // make claude redraw its whole screen so the court sees it from the top
82
+ if (pty) {
83
+ try {
84
+ pty.resize(cols, Math.max(5, rows - 1));
85
+ setTimeout(() => { try { pty.resize(cols, rows); } catch {} }, 80);
86
+ } catch {}
87
+ }
88
+ }
89
+
90
+ if (rt) {
91
+ await tryAttach();
92
+ if (!ws) console.error('어전이 바쁘거나 응답이 없습니다. claude 를 먼저 띄우고, 어전이 비면 자동으로 연결합니다.');
63
93
  } else {
64
- console.error('어전이 꺼져 있습니다(npx eojeon-cli). 그냥 claude 로 실행합니다.');
94
+ console.error('어전이 꺼져 있습니다(npx eojeon-cli). 어전을 켜면 자동으로 연결합니다.');
65
95
  }
66
- const send = (m) => ws && ws.readyState === 1 && ws.send(JSON.stringify(m));
96
+ const retry = setInterval(tryAttach, 3000);
97
+ retry.unref?.();
67
98
 
68
- const claudeArgs = ws && rt.settings && fs.existsSync(rt.settings) ? ['--settings', rt.settings, ...args] : args;
99
+ // hooks: the court's settings when known (they find the court through the token in that file)
100
+ const settings = (readRuntime() || {}).settings;
101
+ const claudeArgs = settings && fs.existsSync(settings) ? ['--settings', settings, ...args] : args;
69
102
  const { spawn } = loadPty();
70
- const pty = spawn(claude, claudeArgs, { name: 'xterm-256color', cols, rows, cwd, env: childEnv() });
103
+ pty = spawn(claude, claudeArgs, { name: 'xterm-256color', cols, rows, cwd, env: childEnv() });
71
104
 
72
105
  pty.onData((d) => {
73
106
  process.stdout.write(d);
74
107
  send({ t: 'tout', d });
75
108
  });
76
- if (ws) {
77
- ws.on('message', (raw) => {
78
- let m;
79
- try { m = JSON.parse(raw.toString()); } catch { return; }
80
- if (m.t === 'input' && typeof m.d === 'string') pty.write(m.d);
81
- });
82
- ws.on('close', () => {
83
- ws = null; // the court went away; keep working locally
84
- });
85
- }
86
109
 
87
110
  if (process.stdin.isTTY) process.stdin.setRawMode(true);
88
111
  process.stdin.setEncoding('utf8');
@@ -96,6 +119,7 @@ export async function run(args) {
96
119
  });
97
120
 
98
121
  pty.onExit(({ exitCode }) => {
122
+ clearInterval(retry);
99
123
  if (process.stdin.isTTY) process.stdin.setRawMode(false);
100
124
  try { ws?.close(); } catch {}
101
125
  process.exit(exitCode ?? 0);