@ictechgy/lterm 0.1.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.
- package/LICENSE +6 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.ko.md +363 -0
- package/README.md +366 -0
- package/bin/lterm.js +84 -0
- package/package.json +44 -0
package/LICENSE
ADDED
package/LICENSE-APACHE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/LICENSE-MIT
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Light Terminal contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# Light Terminal (`lterm`)
|
|
2
|
+
|
|
3
|
+
한국어 | [English](README.md)
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
- **무엇** — Rust로 만든 PTY 세션 데몬 + tmux 호환 shim. 이름이나 pane id로 detach·reattach할 수 있는 영속 세션을 제공합니다.
|
|
8
|
+
- **대상** — Claude Code, Codex CLI, Gemini CLI, `oh-my-codex` / `oh-my-claude` 같은 terminal-first coding agent와, 그것을 `cmux` 안에서 돌리는 사용자.
|
|
9
|
+
- **사용법** — `lterm start`로 만들고 `lterm resume`으로 (재)접속, shim이 적용된 agent 실행에는 `lterm agent <profile>` / `lterm claude` / `lterm codex` / `lterm gemini`. tmux가 켜진 세션 안에서는 `tmux` 명령이 `lterm tmux-compat`으로 해석됩니다.
|
|
10
|
+
- **상태** — alpha MVP. 같은 OS 사용자 안에서 쓰는 편의용 데몬이며, **샌드박스도 escape-sequence sanitizer도 완전한 tmux 대체품도 아닙니다.**
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
AI 에이전트 워크플로를 위해 만든 가벼운 터미널 세션 데몬입니다. tmux 호환 shim을 함께 제공합니다.
|
|
15
|
+
|
|
16
|
+
`lterm`은 tmux 전체를 대체하려는 도구가 아닙니다. 오래 실행되는 PTY 세션을 유지하고, 클라이언트가 자유롭게 detach/reattach할 수 있게 하며, 터미널 escape sequence는 그대로 통과시키고, terminal-first agent 도구가 자주 사용하는 tmux 명령 일부를 호환 shim으로 제공합니다.
|
|
17
|
+
|
|
18
|
+
> **상태:** alpha MVP. 로컬 detached 세션과 호환성 테스트에는 사용할 수 있지만, 아직 완전한 tmux 대체품은 아닙니다.
|
|
19
|
+
>
|
|
20
|
+
> **보안 모델:** `lterm`은 같은 OS 사용자 안에서 쓰는 편의용 데몬이며 샌드박스가 아닙니다. 다른 사용자의 Unix socket 접근은 거부하고 런타임 디렉터리는 소유자 전용 권한으로 만들지만, 같은 OS 사용자 권한으로 실행되는 프로세스는 세션을 제어할 수 있다고 보아야 합니다.
|
|
21
|
+
|
|
22
|
+
## 왜 만들었나
|
|
23
|
+
|
|
24
|
+
다음 세 가지 요구를 충족하는 것이 목표입니다.
|
|
25
|
+
|
|
26
|
+
1. **tmux와 비슷한 세션 지속성과 원격 접속** — 세션은 백그라운드 데몬에서 실행되며, 이름이나 pane id로 attach/detach할 수 있습니다. 원격 호스트에 `lterm`이 설치되어 있다면 `lterm ssh`로 접속할 수 있습니다.
|
|
27
|
+
2. **cmux 호환성** — cmux 안에서 실행할 때는 OSC 알림을 그대로 통과시키고 `lterm notify`를 제공하며, tmux shim은 가능한 경우 worker pane을 cmux native split으로 엽니다.
|
|
28
|
+
3. **AI 도구 지원** — `lterm agent <profile>`, `lterm claude`, `lterm codex`, `lterm gemini`, `lterm omx`, `lterm omc`, `lterm install-shim`은 tmux를 전제하는 agent 도구를 위해 가짜 `tmux` 명령과 `TMUX` / `TMUX_PANE` 환경 변수를 제공합니다.
|
|
29
|
+
|
|
30
|
+
cmux 호환 동작은 cmux가 문서화한 기능을 따릅니다. cmux는 `cmux notify`와 OSC 777 / OSC 99 알림, workspace/split을 위한 Unix socket·CLI API, 그리고 tmux 명령을 cmux pane으로 매핑하는 oh-my-codex 통합을 제공합니다.
|
|
31
|
+
|
|
32
|
+
## 설치
|
|
33
|
+
|
|
34
|
+
Homebrew로 설치:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
brew install ictechgy/tap/lterm
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
지원되는 macOS/Linux 플랫폼에서는 npm으로 설치할 수 있습니다.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g @ictechgy/lterm
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
GitHub에서 Cargo로 설치:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cargo install --git https://github.com/ictechgy/light_terminal --tag v0.1.0
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
이 체크아웃에서 직접 빌드하려면 Rust 1.85 이상이 필요합니다.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cargo build --release --locked
|
|
56
|
+
./target/release/lterm --help
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
개발 중에는 다음처럼 실행할 수 있습니다.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cargo run -- --help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
터미널에서 tmux shim을 사용하려면:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
lterm install-shim
|
|
69
|
+
# 출력된 디렉터리를 실제 tmux보다 앞쪽 PATH에 추가하거나 다음을 실행하세요.
|
|
70
|
+
eval "$(lterm env)"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 빠른 시작
|
|
74
|
+
|
|
75
|
+
**세션을 만들고 바로 attach:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
lterm start -n api -- npm run dev
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**먼저 detached로 만든 뒤 나중에 attach:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
lterm start -d -n api -- npm run dev
|
|
85
|
+
lterm resume api
|
|
86
|
+
|
|
87
|
+
# 호환 이름도 계속 사용할 수 있습니다.
|
|
88
|
+
lterm attach api
|
|
89
|
+
lterm a api
|
|
90
|
+
# `-a`는 `lterm` 바로 뒤에 쓰고, target과는 공백으로 구분하세요.
|
|
91
|
+
lterm -a api
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Agent terminal 명령어 어휘:**
|
|
95
|
+
|
|
96
|
+
| 작업 | 일반 명령 | 호환 이름 |
|
|
97
|
+
| --- | --- | --- |
|
|
98
|
+
| 영속 프로세스 시작 | `lterm start -n api -- npm run dev` | `new` |
|
|
99
|
+
| tmux 호환을 켠 상태로 명령 실행 | `lterm run -- codex exec "요약해줘"` | 없음 (`--no-tmux`로 opt out) |
|
|
100
|
+
| 세션 열기 또는 생성 | `lterm open main` | `attach-or-new` |
|
|
101
|
+
| 기존 세션 재개 | `lterm resume api` | `attach`, `a`, `-a` |
|
|
102
|
+
| 세션 목록 보기 | `lterm sessions` | `list`, `ls` |
|
|
103
|
+
| 프로세스 트리 확인 | `lterm processes api --json` | `ps` |
|
|
104
|
+
| 정제된 scrollback 읽기 | `lterm logs api --start=-80` | `capture` |
|
|
105
|
+
| PTY에 입력 쓰기 | `lterm input api 'echo hello' --enter` | `send` |
|
|
106
|
+
| 세션 종료 | `lterm close api` | `kill` |
|
|
107
|
+
| background daemon 명시 실행 | `lterm daemon` | 없음 |
|
|
108
|
+
| daemon과 모든 세션 종료 | `lterm shutdown` | 없음 |
|
|
109
|
+
|
|
110
|
+
주변 agent/shim 유틸리티도 제품 CLI 명령이며, tmux alias가 아닙니다:
|
|
111
|
+
|
|
112
|
+
| 작업 | 제품 명령 | 호환 경계 |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| profile 기반 agent 세션 실행 | `lterm agent claude -- --help` | sibling shortcuts: `lterm claude`, `lterm codex`, `lterm gemini`, `lterm omx`, `lterm omc` |
|
|
115
|
+
| 사용 가능한 agent profile 확인 | `lterm agents --json` | 실행 시점의 PATH availability probe |
|
|
116
|
+
| `tmux` 호환 shim 설치 | `lterm install-shim` | `lterm tmux-compat`으로 전달하는 shim 생성 |
|
|
117
|
+
| tmux 호환 shell export 출력 | `eval "$(lterm env)"` | 신뢰할 수 있는 shell setup용 고정 `export` 행; 생성 path는 `shlex` quoting 기반의 유효한 POSIX shell token이며 `PATH`는 shim dir을 기존 `$PATH` 앞에 추가 |
|
|
118
|
+
| cmux-friendly 알림 보내기 | `lterm notify --title 'Done' --body 'Tests passed'` | OSC 777 fallback은 C0 whitespace control `\n`, `\r`, `\t`를 제외한 Unicode control을 제거하고, 이 세 control과 semicolon은 공백으로 바꾸며, `U+007F`/`U+0080..U+009F`는 제거하고, control이 아닌 Unicode text는 보존 |
|
|
119
|
+
| 원격 호스트에 attach | `lterm ssh user@host main` | 신뢰할 수 있는 host에서만 사용; host-key 확인은 SSH가 처리하고 remote PTY bytes는 정제 없이 pass-through |
|
|
120
|
+
| tmux shim namespace 직접 호출 | `lterm tmux-compat list-commands` | 제품 alias 표가 아니라 호환 namespace |
|
|
121
|
+
|
|
122
|
+
`lterm env`는 `PATH`의 `lterm` binary를 신뢰할 수 있을 때만 `eval`
|
|
123
|
+
용도로 사용하세요. smoke test는 export-only 출력과 space, `$`, `'`가
|
|
124
|
+
포함된 생성 path가 POSIX shell token으로 round-trip되는지, 그리고 shim
|
|
125
|
+
directory가 기존 `$PATH`를 대체하지 않고 앞에 추가되는지 검증합니다.
|
|
126
|
+
|
|
127
|
+
`lterm ssh`는 remote PTY bytes를 local terminal로 정제 없이 전달하므로,
|
|
128
|
+
compromised remote는 local terminal emulator가 허용하는 control sequence를
|
|
129
|
+
구동할 수 있습니다. 예를 들어 OSC 52 clipboard 쓰기, OSC 8 hyperlink,
|
|
130
|
+
window/title 변경, cursor/screen 조작, bracketed paste 토글, emulator별
|
|
131
|
+
escape 처리가 여기에 포함됩니다. 직접 `ssh`하듯 신뢰할 수 있는 host에만
|
|
132
|
+
사용하고 terminal feature 설정도 그에 맞게 관리하세요. "cmux-friendly"
|
|
133
|
+
알림은 fallback 경로가 cmux가 감시하는 OSC 777 notification 형식을
|
|
134
|
+
출력한다는 뜻입니다. OSC 777 fallback sanitizer는 protocol framing을
|
|
135
|
+
보호하는 범위이며, 신뢰된 title/body 내부의 Unicode bidi/format/zero-width
|
|
136
|
+
문자를 normalize하지 않습니다.
|
|
137
|
+
|
|
138
|
+
호환 이름은 앞에 flag 형태로 표시된 경우를 제외하면 subcommand입니다. `-a`는 기존 shortcut 형태라 `lterm -a <target>`처럼 사용해야 합니다.
|
|
139
|
+
|
|
140
|
+
이 표는 사람과 agent가 직접 쓰는 제품 CLI 표면입니다. `lterm tmux-compat ...`는 이미 tmux 명령을 사용하는 스크립트를 위한 별도 shim namespace이며, 모든 제품 명령에 tmux 호환 이름이 있는 것은 아닙니다. 런타임에 지원되는 shim subset은 `lterm tmux-compat list-commands`로 확인하세요.
|
|
141
|
+
|
|
142
|
+
`lterm sessions`는 기본적으로 하위 pane을 숨기고, 기존 첫 5개 tab-separated 열(`name`, `pane`, `alive`, `cwd`, `command`)을 유지한 뒤 attach 상태(`attached` / `detached`)와 parent pane(`-` 또는 pane id)을 뒤에 붙입니다. 호환 이름인 `lterm list`와 `lterm ls`도 같은 출력 형식을 유지합니다. attach된 클라이언트는 아래쪽 한 줄에 파란 상태 바를 표시하고, PTY는 그 줄을 제외한 영역으로 resize됩니다. 예전처럼 전체 터미널을 raw 모드로 재개하려면 `lterm resume --no-status api`(호환 이름: `lterm attach --no-status api`)를 쓰거나, 상태줄 처리가 충돌하는 클라이언트에서는 `LTERM_NO_STATUS=1` / `LTERM_STATUS=0`을 설정하세요.
|
|
143
|
+
|
|
144
|
+
`LTERM_STATUS_STYLE=full` 또는 `LTERM_STATUS_STYLE=minimal` 로 시각 스타일을 선택할 수 있습니다. `full`(로컬 터미널 기본값)은 검정 글자 + bright-blue 배경, `minimal`은 SGR 색을 모두 생략한 plain text로 동작합니다. SSH 세션(`SSH_CONNECTION` / `SSH_CLIENT` / `SSH_TTY` 감지)에서는 자동으로 `minimal`이 적용되어 Termius 같은 모바일 SSH 클라이언트의 색 충돌을 줄입니다.
|
|
145
|
+
|
|
146
|
+
attach된 PTY가 alternate screen buffer로 진입하면(예: `vim`, `less`, `htop`이 `\x1b[?1049h` 사용) lterm은 status bar를 일시 중단해 alt-screen 앱의 UI와 충돌을 피합니다. 앱이 alt-screen을 종료하는 즉시 status bar가 다시 그려집니다.
|
|
147
|
+
|
|
148
|
+
`lterm resume` / `lterm attach` 도중 panic 이나 abort 가 발생해도 process-wide hook 이 최소 복구 sequence (scroll region 리셋, 커서 보이기, alt-screen 종료, SGR 리셋) 를 emit 해 사용자 터미널이 raw mode 나 hidden cursor 상태로 남지 않습니다.
|
|
149
|
+
|
|
150
|
+
CJK 문자나 이모지(ZWJ family, 국기, 결합 문자 포함)가 들어간 세션 이름은 `unicode-width` / `unicode-segmentation` 으로 디스플레이 폭을 계산해 정렬되므로 wide character가 섞여도 status bar 패딩이 어긋나지 않습니다.
|
|
151
|
+
|
|
152
|
+
child 애플리케이션이 `CSI u` enhancement sequence로 Kitty keyboard protocol을 켜면, lterm은 이를 추적했다가 attach 종료 시 terminal keyboard mode를 best-effort로 복원합니다. 그래서 child가 비정상 종료된 뒤 shell 입력이 `1;1:3u` 같은 escape 조각으로 보이는 상황을 줄입니다.
|
|
153
|
+
|
|
154
|
+
**세션 확인 및 제어:**
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
lterm sessions
|
|
158
|
+
lterm sessions --children
|
|
159
|
+
lterm sessions --all
|
|
160
|
+
lterm processes api
|
|
161
|
+
lterm logs api --start=-80
|
|
162
|
+
lterm input api 'echo hello' --enter
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
위의 일반 alias는 tmux 용어를 몰라도 agent terminal을 일상적으로 다루기 쉽게 하기 위한 표면입니다. `sessions`는 영속 작업을 나열하고, `processes`는 child process tree를 확인하고, `logs`는 정제된 scrollback을 읽고, `input`은 대상 PTY에 텍스트를 씁니다. 호환 이름은 visible alias로 유지되어 스크립트와 기존 사용 습관에서도 계속 사용할 수 있습니다: `list` / `ls`, `ps`, `capture`, `send`.
|
|
166
|
+
|
|
167
|
+
**세션 종료:**
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
lterm close api
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`kill`은 `close`의 visible compatibility alias입니다. 두 이름 모두 같은 session/pane 종료 경로를 사용합니다.
|
|
174
|
+
|
|
175
|
+
**daemon 명시 실행 (고급):**
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
# 일반 client 명령은 필요할 때 daemon을 시작합니다. supervisor/debugging 용도로 직접 실행하세요.
|
|
179
|
+
lterm daemon
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**daemon과 그 daemon이 소유한 모든 세션 종료:**
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# 단일 세션 close가 아니라 daemon-wide 종료입니다.
|
|
186
|
+
lterm shutdown
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## AI 워크플로
|
|
190
|
+
|
|
191
|
+
**자주 쓰는 agent CLI를 shim이 적용된 세션에서 실행:**
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
lterm claude
|
|
195
|
+
lterm codex
|
|
196
|
+
lterm gemini -- -p "이 저장소를 요약해줘"
|
|
197
|
+
lterm agents
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
위 명령은 다음 profile 명령의 얇은 alias입니다.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
lterm agent claude
|
|
204
|
+
lterm agent codex
|
|
205
|
+
lterm agent gemini -- -p "이 저장소를 요약해줘"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
agent launcher는 built-in profile과 custom `lterm agent <profile>` 실행에서 같은 세션 제어 옵션을 받습니다.
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
lterm claude --name repo-review --cwd /path/to/repo
|
|
212
|
+
lterm codex --detach --name repo-codex -- exec "이 저장소를 요약해줘"
|
|
213
|
+
lterm gemini --status -- -p "lterm status를 유지해줘"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Claude/Codex/Gemini profile은 각 도구의 자체 TUI/status/alternate-screen 렌더링과 충돌하지 않도록 기본적으로 lterm status bar를 끈 raw full-terminal attach를 사용합니다. `--status`로 lterm status bar를 강제로 켜거나, 기본값이 켜진 profile에서는 `--no-status`로 끌 수 있습니다. agent에 넘길 인자가 lterm launch option처럼 보일 수 있으면 앞에 `--`를 두세요. 직접 generic tmux-compatible primitive를 쓰거나 아직 profile이 없는 미래 agent를 실행하려면 `lterm run -- <command>`를 사용하세요. `run`은 shim을 기본으로 켜며 `--no-tmux`로 끌 수 있습니다.
|
|
217
|
+
|
|
218
|
+
launcher 제어 옵션은 agent의 흔한 short flag(`-c` 등)를 빼앗지 않도록 long-only(`--name`, `--cwd`, `--detach`, `--status`, `--no-status`)입니다. 이 옵션들은 `claude`, `codex`, `gemini`, `omx`, `omc`, `agent <profile>`에 동일하게 적용됩니다.
|
|
219
|
+
`--detach`는 각 field의 control character와 Unicode line/paragraph separator를 공백으로 바꾼 `name<TAB>pane<TAB>command`를 출력하며, 나중에 `lterm resume <name>` 또는 호환 이름 `lterm attach <name>`으로 다시 붙으면 됩니다. detach record에는 `--cwd`가 포함되지 않으므로 나중에 필요하면 session을 조회하세요.
|
|
220
|
+
명시한 `--name`은 lterm의 일반 session-name 문법을 따르고 사용 중이지 않아야 합니다. 충돌 시 자동 suffix를 붙이지 않고 conflict error로 실패합니다.
|
|
221
|
+
이름에는 ASCII 문자/숫자와 `.`, `_`, `-`만 사용할 수 있고, `-` 또는 `%`로 시작할 수 없으며, UUID처럼 보이면 안 되고, 128바이트를 넘을 수 없습니다.
|
|
222
|
+
`lterm agents` 또는 `lterm agents --json`으로 profile 기본값과 현재 `PATH`에서 binary를 찾을 수 있는지 확인할 수 있습니다. JSON row의 `kind` 값은 `built-in`, `custom`, `configured` 중 하나입니다. `lterm agents codex my-agent --json`처럼 profile 이름을 넘기면 선택한 built-in/custom/configured profile만 확인합니다. availability는 실행 시점의 PATH probe입니다.
|
|
223
|
+
반복해서 쓰는 custom alias는 명시적인 JSON config 파일로 넘길 수 있습니다.
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
cat > agents.json <<'JSON'
|
|
227
|
+
{ "profiles": [{ "name": "repo-review", "binary": "codex", "session_base": "repo-review-session", "status_default": false }] }
|
|
228
|
+
JSON
|
|
229
|
+
lterm agents --agent-config agents.json --json
|
|
230
|
+
lterm agent repo-review --agent-config agents.json -- exec "이 저장소를 리뷰해줘"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
configured name과 binary는 `lterm agent <profile>`과 같은 안전한 profile 문법을 사용하며, built-in 이름은 재정의할 수 없습니다.
|
|
234
|
+
`binary`는 shell fragment나 path가 아니라 `PATH`에서 찾는 bare command name이어야 합니다. `binary`는 기본적으로 `name`, `session_base`는 `<name>-lterm`, `status_default`는 `true`이며 field가 있을 때는 boolean이어야 합니다. 중복 이름과 알 수 없는 JSON field는 거부됩니다. `--agent-config`를 넘긴 경우 built-in이 아닌 선택 이름은 그 파일 안에 있어야 합니다.
|
|
235
|
+
|
|
236
|
+
**Oh My Codex를 shim이 적용된 세션에서 실행:**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
lterm omx team
|
|
240
|
+
# omx에 넘길 추가 flag는 그대로 전달됩니다.
|
|
241
|
+
lterm omx --madmax --xhigh
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Oh My Claude도 같은 방식으로 실행:**
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
lterm omc team
|
|
248
|
+
# 본 README 작성 시점에 테스트한 OMC 빌드는 --xhigh를 거부합니다.
|
|
249
|
+
# 설치된 `omc --help`에 해당 flag가 보이지 않는다면 --xhigh 없이 --madmax만 사용하세요.
|
|
250
|
+
lterm omc --madmax
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**임의의 명령을 tmux 호환 모드로 실행:**
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
lterm run -- omx hud --tmux
|
|
257
|
+
lterm run -- claude
|
|
258
|
+
lterm run -- codex exec "저장소를 요약해줘"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
이 세션 안에서는 `tmux`가 `lterm tmux-compat` shim으로 해석됩니다. 이 shim은 호환 계층이지 모든 `lterm` 제품 명령의 두 번째 철자가 아닙니다. 현재 shim은 AI orchestration 스크립트가 자주 사용하는 다음 명령 subset을 구현합니다.
|
|
262
|
+
|
|
263
|
+
- **세션** — `new-session`, `attach-session`, `has-session`, `list-sessions`, `kill-session`
|
|
264
|
+
- **조회** — `list-windows`, `list-clients`, `list-commands`, `show-options`, `show-window-options`
|
|
265
|
+
- **Pane** — `split-window`, `list-panes`, `display-message`, `capture-pane`, `send-keys`, `kill-pane`, `resize-pane`
|
|
266
|
+
- **Buffer / popup** — `display-popup`, `wait-for`, `load-buffer`, `save-buffer`, `paste-buffer`
|
|
267
|
+
- **호환용 no-op** — `select-pane`, `select-layout`, `set-option`, `set-window-option`, `set-environment`, `show-environment`
|
|
268
|
+
|
|
269
|
+
호환성 참고: lterm은 각 root session을 하나의 pseudo-window로 모델링합니다
|
|
270
|
+
(`window_index=0`, `window_panes=1`). lterm은 client별 process/TTY metadata를
|
|
271
|
+
노출하지 않기 때문에 `client_pid`와 `client_tty`는 빈 문자열로 확장됩니다.
|
|
272
|
+
tmux `-f` filter는 조용히 무시하지 않고 의도적으로 거부합니다.
|
|
273
|
+
|
|
274
|
+
## cmux 동작
|
|
275
|
+
|
|
276
|
+
`lterm tmux-compat split-window`가 cmux 환경(`CMUX_WORKSPACE_ID`, `CMUX_SURFACE_ID`, 또는 cmux socket)을 감지하면 다음 순서로 동작합니다.
|
|
277
|
+
|
|
278
|
+
1. worker 명령을 위한 새 `lterm` PTY 세션을 시작합니다.
|
|
279
|
+
2. cmux에 native split 생성을 요청합니다 (`cmux new-split right/down`).
|
|
280
|
+
3. `LTERM_BIN`이 `resume`을 모르는 구버전 `lterm`을 가리켜도 cmux pane이 계속 동작하도록, 생성된 split에는 호환 명령인 `lterm attach <pane>`을 보냅니다.
|
|
281
|
+
|
|
282
|
+
이렇게 하면 실제 pane은 cmux가 그리고, scrollback capture와 `send-keys` 호환은 `lterm`이 유지합니다.
|
|
283
|
+
|
|
284
|
+
**알림:**
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
lterm notify --title 'Task complete' --body 'All checks passed'
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`lterm notify`는 먼저 `cmux notify`를 시도합니다. 사용할 수 없으면 OSC 777을 출력해 cmux나 호환 터미널이 알림을 표시할 수 있도록 합니다. fallback OSC에 들어가는 알림 필드는 터미널 제어 문자를 제거한 뒤 출력하며, subtitle/body 구분용 newline 같은 문자는 서로 붙지 않도록 공백으로 정규화합니다.
|
|
291
|
+
|
|
292
|
+
## 원격 접속
|
|
293
|
+
|
|
294
|
+
원격 머신에 `lterm`이 설치되어 있다면:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
lterm ssh user@host main
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
원격 호스트에서는 `lterm open main`과 같은 attach-or-create 동작을 사용합니다. 구버전 원격 `lterm`이 `open`을 모르더라도 새 로컬 클라이언트가 계속 접속할 수 있도록 실제 wire command는 `lterm attach-or-new main`으로 유지합니다. SSH 옵션은 `--` 뒤에 전달할 수 있습니다.
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
lterm ssh devbox main -- -p 2222 -i ~/.ssh/id_ed25519
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## 구조
|
|
307
|
+
|
|
308
|
+
- **Daemon** — 사용자별 Unix socket 하나를 `$XDG_RUNTIME_DIR` 아래에 만들고, 없으면 `/tmp` 아래 소유자 전용 fallback 경로를 사용합니다.
|
|
309
|
+
- **PTY 세션** — `portable-pty`로 실행하며 ring-buffer scrollback을 유지합니다.
|
|
310
|
+
- **Attach protocol** — CLI가 Unix socket으로 JSON을 보낸 뒤, 선택적으로 로컬 상태 바를 위해 아래쪽 한 줄을 예약하고 PTY byte stream을 전달합니다.
|
|
311
|
+
- **tmux shim** — `tmux`라는 작은 shell script가 명령을 `lterm tmux-compat`으로 넘깁니다.
|
|
312
|
+
- **cmux bridge** — cmux가 감지되면 cmux CLI를 사용합니다 (선택).
|
|
313
|
+
|
|
314
|
+
`lterm` binary를 업그레이드한 뒤 새 wire-protocol 동작에 의존하려면 이미
|
|
315
|
+
떠 있는 daemon을 재시작하세요. 실행 중인 daemon process는 종료 전까지
|
|
316
|
+
기존 코드를 계속 사용합니다.
|
|
317
|
+
|
|
318
|
+
## 보안 메모
|
|
319
|
+
|
|
320
|
+
**터미널 출력은 그대로 전달됩니다.** `lterm resume`(호환 이름: `lterm attach`)은 full-screen 터미널 프로그램과 cmux/OSC 알림이 정상 동작하도록 PTY byte를 그대로 통과시킵니다. 로컬 상태 바는 클라이언트 쪽 표시 요소일 뿐이며, 완전한 raw 모드 터미널이 필요하면 `--no-status`를 사용하세요. 신뢰할 수 없는 child 프로그램은 tmux/screen에서와 마찬가지로 attach된 터미널에 escape sequence를 출력할 수 있습니다. **`lterm`을 escape-sequence sanitizer나 sandbox로 사용하지 마세요.**
|
|
321
|
+
|
|
322
|
+
**Capture 출력은 사람/AI가 읽기 쉽도록 정제됩니다.** `lterm logs`(호환 이름: `lterm capture`)와 `tmux capture-pane`은 captured scrollback을 출력할 때 일반적인 터미널 제어 시퀀스를 제거합니다.
|
|
323
|
+
|
|
324
|
+
**프로세스 가시성.** `lterm processes [session]`(호환 이름: `lterm ps [session]`)은 각 세션 child 아래의 process tree를 보여 줍니다. Codex/OMX/MCP subprocess가 누적되어 메모리 누수처럼 커지기 전에 확인하는 용도입니다. 시스템 `ps`는 절대 경로로 호출하며, 형식이 잘못된 process row는 추측하지 않고 건너뜁니다.
|
|
325
|
+
|
|
326
|
+
**Socket 위치.** 커스텀 `LTERM_SOCKET` 경로는 소유자 전용 디렉터리 안에 있어야 합니다. 격리된 socket 위치가 필요할 때는 `LTERM_RUNTIME_DIR`를 우선 사용하세요.
|
|
327
|
+
|
|
328
|
+
**Popup 명령.** `tmux-compat display-popup`은 tmux와 비슷한 동작을 위해 요청된 명령을 사용자 shell로 실행합니다. **신뢰할 수 없는 popup 명령을 전달하지 마세요.**
|
|
329
|
+
|
|
330
|
+
**빌드 재현성.** 릴리스 빌드는 커밋된 lockfile을 사용하세요: `cargo build --release --locked`. 현재 lockfile은 `serde_json 1.0.149`를 고정합니다. 이 버전의 transitive dependency인 `zmij`는 docs.rs/crates.io의 공식 serde_json package metadata에 등록된 의존성이며, 로컬에 vendor된 crate가 아닙니다.
|
|
331
|
+
|
|
332
|
+
## 현재 제한 사항
|
|
333
|
+
|
|
334
|
+
- 세션 지속성은 데몬과 호스트가 동작 중일 때만 유지됩니다. 재부팅 후 프로세스 상태 복원은 아직 구현하지 않았습니다.
|
|
335
|
+
- cmux 밖에서 `split-window`는 추가 managed PTY 세션을 만들지만, 터미널 안에 tiled UI를 직접 그리지는 않습니다.
|
|
336
|
+
- 이 프로젝트는 완전한 tmux server가 아니라 호환 subset만 제공합니다. 고급 tmux format/option을 사용하는 스크립트는 shim 명령 추가가 필요할 수 있습니다.
|
|
337
|
+
- cmux pane capture는 cmux scrollback API가 아니라 `lterm` 세션을 통해 처리합니다.
|
|
338
|
+
- 데몬은 로컬 클라이언트를 OS peer credential과 소유자 전용 socket 경로로 인증합니다. 세션별 ACL은 아직 없습니다.
|
|
339
|
+
- 세션 종료는 verified process-group signaling을 사용하므로 `shell → OMX → Codex → MCP` 같은 child tree는 가능한 한 함께 정리됩니다. 의도적으로 다른 session/process group으로 detach한 프로세스는 `lterm close` / `lterm kill` 이후에도 살아 있을 수 있으니, `lterm processes` / `lterm ps`나 OS process 도구로 확인하세요.
|
|
340
|
+
|
|
341
|
+
## 개발
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
cargo fmt
|
|
345
|
+
cargo test
|
|
346
|
+
cargo build --locked
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
수동 테스트에는 격리된 runtime directory 사용을 권장합니다.
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
TMP=$(mktemp -d)
|
|
353
|
+
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- start --name test -- sh -lc 'echo hi; sleep 10'
|
|
354
|
+
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- logs test -S=-20
|
|
355
|
+
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- shutdown
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## 라이선스
|
|
359
|
+
|
|
360
|
+
다음 두 라이선스 중 하나를 선택해 사용할 수 있습니다.
|
|
361
|
+
|
|
362
|
+
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
|
|
363
|
+
- MIT License ([LICENSE-MIT](LICENSE-MIT))
|
package/README.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Light Terminal (`lterm`)
|
|
2
|
+
|
|
3
|
+
[한국어](README.ko.md) | English
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
- **What** — A Rust-based PTY session daemon with a tmux-compatible shim. Persistent sessions you can detach and reattach by name or pane id.
|
|
8
|
+
- **Who it's for** — Terminal-first coding agents such as Claude Code, Codex CLI, Gemini CLI, `oh-my-codex` / `oh-my-claude`, and users running them inside `cmux`.
|
|
9
|
+
- **How** — `lterm start` to create, `lterm resume` to (re)connect, `lterm agent <profile>` / `lterm claude` / `lterm codex` / `lterm gemini` for shimmed agent runs. Inside a tmux-enabled session, the `tmux` command resolves to `lterm tmux-compat`.
|
|
10
|
+
- **Status** — alpha MVP. A same-user convenience daemon — **not** a sandbox, an escape-sequence sanitizer, or a full tmux replacement.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
A lightweight terminal session daemon with a tmux-compatible shim, built for AI-agent workflows.
|
|
15
|
+
|
|
16
|
+
`lterm` is intentionally smaller than tmux. It keeps long-running PTY sessions alive, lets clients detach and reattach at will, forwards terminal escape sequences unchanged, and translates the subset of tmux commands commonly used by terminal-first agent tooling.
|
|
17
|
+
|
|
18
|
+
> **Status:** alpha MVP. Usable for local detached sessions and compatibility testing — not yet a full tmux replacement.
|
|
19
|
+
>
|
|
20
|
+
> **Security model:** `lterm` is a same-user convenience daemon, not a sandbox. It rejects cross-user Unix-socket peers and uses owner-only runtime directories, but any process running as your OS user should be considered capable of controlling your sessions.
|
|
21
|
+
|
|
22
|
+
## Why this exists
|
|
23
|
+
|
|
24
|
+
The project addresses three constraints:
|
|
25
|
+
|
|
26
|
+
1. **tmux-like persistence and remote access** — sessions run inside a background daemon and can be attached or detached by name or pane id. Remote access is available through `lterm ssh`, provided `lterm` is installed on the remote host.
|
|
27
|
+
2. **cmux compatibility** — when running inside cmux, `lterm` preserves OSC notifications, exposes `lterm notify`, and the tmux shim opens worker panes as native cmux splits when possible.
|
|
28
|
+
3. **AI tooling support** — `lterm agent <profile>`, `lterm claude`, `lterm codex`, `lterm gemini`, `lterm omx`, `lterm omc`, and `lterm install-shim` provide a fake `tmux` command and the `TMUX` / `TMUX_PANE` environment variables that agent tools expect.
|
|
29
|
+
|
|
30
|
+
cmux compatibility is grounded in cmux's documented behavior: notifications via `cmux notify` and OSC 777 / OSC 99, a Unix-socket/CLI API for workspaces and splits, and a tmux shim that maps tmux commands into native cmux panes.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
With Homebrew:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
brew install ictechgy/tap/lterm
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
With npm on supported macOS/Linux platforms:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g @ictechgy/lterm
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
With Cargo from GitHub:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cargo install --git https://github.com/ictechgy/light_terminal --tag v0.1.0
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
From this checkout, use Rust 1.85 or newer:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cargo build --release --locked
|
|
56
|
+
./target/release/lterm --help
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For local development:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
cargo run -- --help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
To expose the tmux shim:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
lterm install-shim
|
|
69
|
+
# Add the printed directory to PATH ahead of the real tmux, or eval the helper:
|
|
70
|
+
eval "$(lterm env)"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Quick start
|
|
74
|
+
|
|
75
|
+
**Create a persistent session and attach immediately:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
lterm start -n api -- npm run dev
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Create detached, attach later:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
lterm start -d -n api -- npm run dev
|
|
85
|
+
lterm resume api
|
|
86
|
+
|
|
87
|
+
# Compatibility names remain available:
|
|
88
|
+
lterm attach api
|
|
89
|
+
lterm a api
|
|
90
|
+
# `-a` goes right after `lterm`, separated from the target by a space.
|
|
91
|
+
lterm -a api
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Agent-terminal command vocabulary:**
|
|
95
|
+
|
|
96
|
+
| Task | General command | Compatibility names |
|
|
97
|
+
| --- | --- | --- |
|
|
98
|
+
| Start a persistent process | `lterm start -n api -- npm run dev` | `new` |
|
|
99
|
+
| Run a command with tmux compatibility enabled | `lterm run -- codex exec "summarize"` | None (`--no-tmux` opts out) |
|
|
100
|
+
| Open or create a session | `lterm open main` | `attach-or-new` |
|
|
101
|
+
| Resume an existing session | `lterm resume api` | `attach`, `a`, `-a` |
|
|
102
|
+
| List sessions | `lterm sessions` | `list`, `ls` |
|
|
103
|
+
| Inspect process trees | `lterm processes api --json` | `ps` |
|
|
104
|
+
| Read sanitized scrollback | `lterm logs api --start=-80` | `capture` |
|
|
105
|
+
| Write input to a PTY | `lterm input api 'echo hello' --enter` | `send` |
|
|
106
|
+
| Stop a session | `lterm close api` | `kill` |
|
|
107
|
+
| Run the background daemon explicitly | `lterm daemon` | None |
|
|
108
|
+
| Stop the daemon and all sessions | `lterm shutdown` | None |
|
|
109
|
+
|
|
110
|
+
Adjacent agent/shim utility commands are also product CLI commands, not tmux
|
|
111
|
+
aliases:
|
|
112
|
+
|
|
113
|
+
| Task | Product command | Compatibility boundary |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| Launch a profiled agent session | `lterm agent claude -- --help` | Sibling shortcuts: `lterm claude`, `lterm codex`, `lterm gemini`, `lterm omx`, `lterm omc` |
|
|
116
|
+
| Inspect available agent profiles | `lterm agents --json` | PATH availability probe at command runtime |
|
|
117
|
+
| Install the `tmux` compatibility shim | `lterm install-shim` | Creates a shim that forwards to `lterm tmux-compat` |
|
|
118
|
+
| Print shell exports for tmux compatibility | `eval "$(lterm env)"` | Fixed `export` lines for trusted shell setup; generated paths are valid POSIX shell tokens from `shlex` quoting, and `PATH` prepends the shim dir to `$PATH` |
|
|
119
|
+
| Send a cmux-friendly notification | `lterm notify --title 'Done' --body 'Tests passed'` | OSC 777 fallback drops Unicode controls except the C0 whitespace controls `\n`, `\r`, and `\t`, replaces those controls and semicolons with spaces, drops `U+007F`/`U+0080..U+009F`, and preserves non-control Unicode text |
|
|
120
|
+
| Attach to a remote host | `lterm ssh user@host main` | Use trusted hosts; SSH handles host-key checks, and remote PTY bytes pass through without sanitization |
|
|
121
|
+
| Call the tmux shim namespace directly | `lterm tmux-compat list-commands` | Compatibility namespace, not a product alias table |
|
|
122
|
+
|
|
123
|
+
`lterm env` is meant for `eval` only when you trust the `lterm` binary on
|
|
124
|
+
your `PATH`; its smoke coverage requires export-only output, verifies that
|
|
125
|
+
generated paths containing spaces, `$`, and `'` round-trip as POSIX shell tokens,
|
|
126
|
+
and confirms the shim directory extends the existing `$PATH` rather than
|
|
127
|
+
replacing it.
|
|
128
|
+
|
|
129
|
+
`lterm ssh` forwards remote PTY bytes to the local terminal without sanitizing
|
|
130
|
+
terminal control sequences, so a compromised remote can drive terminal features
|
|
131
|
+
that your local emulator permits: OSC 52 clipboard writes, OSC 8 hyperlinks,
|
|
132
|
+
window/title changes, cursor or screen manipulation, bracketed paste toggles, and
|
|
133
|
+
any emulator-specific escape handling. Treat it like direct `ssh` to a trusted
|
|
134
|
+
host and configure terminal features accordingly. "cmux-friendly" notification
|
|
135
|
+
means the fallback path emits the OSC 777 notification format that cmux watches.
|
|
136
|
+
The OSC 777 fallback sanitizer protects protocol framing; it does not normalize
|
|
137
|
+
Unicode bidi, format, or zero-width characters inside trusted title/body text.
|
|
138
|
+
|
|
139
|
+
Compatibility names are subcommands unless shown as a leading flag: `-a` is the legacy shortcut form and must be used as `lterm -a <target>`.
|
|
140
|
+
|
|
141
|
+
This table is the product CLI surface for humans and agents. `lterm tmux-compat ...` is a separate shim namespace for scripts that already speak tmux; not every product command has a tmux-compatible spelling. Use `lterm tmux-compat list-commands` to inspect the supported shim subset at runtime.
|
|
142
|
+
|
|
143
|
+
`lterm sessions` hides child panes by default, preserves the original first five tab-separated columns (`name`, `pane`, `alive`, `cwd`, `command`), then appends attach state (`attached` / `detached`) and parent pane (`-` or a pane id). The compatibility names `lterm list` and `lterm ls` keep the same output shape. Attached clients render a small blue status bar on the bottom row showing the current session and pane; the PTY is resized to the remaining rows. For the older raw full-terminal resume, use `lterm resume --no-status api` (or compatibility name `lterm attach --no-status api`), or set `LTERM_NO_STATUS=1` / `LTERM_STATUS=0` for clients whose status-line handling conflicts with lterm.
|
|
144
|
+
|
|
145
|
+
Set `LTERM_STATUS_STYLE=full` or `LTERM_STATUS_STYLE=minimal` to choose the visual style. `full` (default for local terminals) shows black text on a bright-blue background; `minimal` drops all SGR colors in favor of plain text. SSH sessions (detected via `SSH_CONNECTION`, `SSH_CLIENT`, or `SSH_TTY`) default to `minimal` to avoid color-mapping issues on mobile SSH clients like Termius.
|
|
146
|
+
|
|
147
|
+
When the attached PTY enters the alternate screen buffer (e.g. `vim`, `less`, `htop` via `\x1b[?1049h`), lterm suspends its status bar to avoid conflicting with the application's UI. The status bar is redrawn immediately when the application exits alt-screen.
|
|
148
|
+
|
|
149
|
+
If `lterm resume` / `lterm attach` panics or aborts mid-session, a process-wide hook emits a minimal recovery sequence (scroll region reset, cursor visible, alt-screen exit, SGR reset) so the user's terminal isn't left in raw mode or with hidden cursor.
|
|
150
|
+
|
|
151
|
+
Session names containing CJK characters or emoji (including ZWJ families, country flags, and combining marks) are aligned by display width using `unicode-width` and `unicode-segmentation`, so the status bar stays correctly padded across mixed-width content.
|
|
152
|
+
|
|
153
|
+
When a child application enables the Kitty keyboard protocol through `CSI u` enhancement sequences, lterm tracks that and best-effort restores the terminal keyboard mode when attach exits so a crashed child does not leave later shell input looking like `1;1:3u` escape fragments.
|
|
154
|
+
|
|
155
|
+
**Inspect or control a session:**
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
lterm sessions
|
|
159
|
+
lterm sessions --children
|
|
160
|
+
lterm sessions --all
|
|
161
|
+
lterm processes api
|
|
162
|
+
lterm logs api --start=-80
|
|
163
|
+
lterm input api 'echo hello' --enter
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The generic aliases above are meant for day-to-day agent-terminal use: `sessions` lists persistent work, `processes` inspects child process trees, `logs` reads sanitized scrollback, and `input` writes text to the target PTY. The compatibility names are visible aliases that remain available for scripts and muscle memory: `list` / `ls`, `ps`, `capture`, and `send`.
|
|
167
|
+
|
|
168
|
+
**Stop a session:**
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
lterm close api
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`kill` is a visible compatibility alias for `close`; both names use the same session/pane termination path.
|
|
175
|
+
|
|
176
|
+
**Run the daemon explicitly (advanced):**
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Client commands start this on demand; run it directly for supervisors/debugging.
|
|
180
|
+
lterm daemon
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Stop the daemon and every session it owns:**
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# This is daemon-wide, not a single-session close.
|
|
187
|
+
lterm shutdown
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## AI workflows
|
|
191
|
+
|
|
192
|
+
**Run common agent CLIs inside shimmed sessions:**
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
lterm claude
|
|
196
|
+
lterm codex
|
|
197
|
+
lterm gemini -- -p "summarize this repo"
|
|
198
|
+
lterm agents
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
These are thin profile aliases for:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
lterm agent claude
|
|
205
|
+
lterm agent codex
|
|
206
|
+
lterm agent gemini -- -p "summarize this repo"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Agent launchers accept the same session controls across built-in profiles and custom `lterm agent <profile>` launches:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
lterm claude --name repo-review --cwd /path/to/repo
|
|
213
|
+
lterm codex --detach --name repo-codex -- exec "summarize this repo"
|
|
214
|
+
lterm gemini --status -- -p "keep lterm status visible"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Known Claude/Codex/Gemini profiles default to a raw full-terminal attach without the lterm status bar, so their own TUI/status/alternate-screen rendering stays in control. Use `--status` to force the lterm status bar on, or `--no-status` to force it off for profiles that default on. Put `--` before agent arguments that could be parsed as lterm launch options. Use `lterm run -- <command>` when you want the generic tmux-compatible primitive directly or need to launch an unprofiled future agent; `run` enables the shim by default and `--no-tmux` opts out.
|
|
218
|
+
|
|
219
|
+
Launcher controls are long-only (`--name`, `--cwd`, `--detach`, `--status`, `--no-status`) so common agent short flags such as `-c` pass through naturally. They apply uniformly to `claude`, `codex`, `gemini`, `omx`, `omc`, and `agent <profile>`.
|
|
220
|
+
`--detach` prints `name<TAB>pane<TAB>command` with control characters and Unicode line/paragraph separators in each field replaced by spaces; resume later with `lterm resume <name>` or compatibility name `lterm attach <name>`. The detach record does not echo `--cwd`; query the session if you need to inspect it later.
|
|
221
|
+
Explicit `--name` values use lterm's normal session-name syntax and must be free; they do not auto-suffix on conflict, so an in-use name fails with a conflict error.
|
|
222
|
+
Names may contain ASCII letters, digits, `.`, `_`, and `-`, must not start with `-` or `%`, must not look like a UUID, and are limited to 128 bytes.
|
|
223
|
+
Use `lterm agents` (or `lterm agents --json`) to inspect profile defaults and whether their binaries are currently available in `PATH`. JSON rows use `kind` values of `built-in`, `custom`, or `configured`. Pass profile names, such as `lterm agents codex my-agent --json`, to inspect a selected built-in/custom/configured set; availability is a point-in-time PATH probe.
|
|
224
|
+
For reusable custom aliases, pass an explicit JSON config file:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
cat > agents.json <<'JSON'
|
|
228
|
+
{ "profiles": [{ "name": "repo-review", "binary": "codex", "session_base": "repo-review-session", "status_default": false }] }
|
|
229
|
+
JSON
|
|
230
|
+
lterm agents --agent-config agents.json --json
|
|
231
|
+
lterm agent repo-review --agent-config agents.json -- exec "review this repo"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Configured names and binaries use the same safe profile syntax as `lterm agent <profile>`; built-in names cannot be redefined.
|
|
235
|
+
`binary` must be a bare command name resolved from `PATH`, not a shell fragment or path. `binary` defaults to `name`, `session_base` defaults to `<name>-lterm`, `status_default` defaults to `true` and must be a boolean when present, duplicate names and unknown JSON fields are rejected, and when `--agent-config` is supplied non-built-in selected names must exist in that file.
|
|
236
|
+
|
|
237
|
+
**Run Oh My Codex inside a shimmed session:**
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
lterm omx team
|
|
241
|
+
# Extra omx flags are passed through, e.g.:
|
|
242
|
+
lterm omx --madmax --xhigh
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Run Oh My Claude similarly:**
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
lterm omc team
|
|
249
|
+
# The OMC builds tested here reject --xhigh — use --madmax alone unless your
|
|
250
|
+
# installed `omc --help` explicitly lists --xhigh.
|
|
251
|
+
lterm omc --madmax
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Run any command with tmux compatibility enabled:**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
lterm run -- omx hud --tmux
|
|
258
|
+
lterm run -- claude
|
|
259
|
+
lterm run -- codex exec "summarize the repository"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Inside that session, `tmux` resolves to the `lterm tmux-compat` shim. This is a compatibility layer, not a second spelling of every `lterm` product command. The shim implements the command subset most AI orchestration scripts rely on:
|
|
263
|
+
|
|
264
|
+
- **Sessions** — `new-session`, `attach-session`, `has-session`, `list-sessions`, `kill-session`
|
|
265
|
+
- **Queries** — `list-windows`, `list-clients`, `list-commands`, `show-options`, `show-window-options`
|
|
266
|
+
- **Panes** — `split-window`, `list-panes`, `display-message`, `capture-pane`, `send-keys`, `kill-pane`, `resize-pane`
|
|
267
|
+
- **Buffers / popups** — `display-popup`, `wait-for`, `load-buffer`, `save-buffer`, `paste-buffer`
|
|
268
|
+
- **No-op compatibility** — `select-pane`, `select-layout`, `set-option`, `set-window-option`, `set-environment`, `show-environment`
|
|
269
|
+
|
|
270
|
+
Compatibility notes: lterm models each root session as one pseudo-window
|
|
271
|
+
(`window_index=0`, `window_panes=1`). `client_pid` and `client_tty` expand to
|
|
272
|
+
empty strings because lterm does not expose per-client process or TTY metadata.
|
|
273
|
+
tmux `-f` filters are intentionally rejected instead of being silently ignored.
|
|
274
|
+
|
|
275
|
+
## cmux behavior
|
|
276
|
+
|
|
277
|
+
When `lterm tmux-compat split-window` detects cmux (via `CMUX_WORKSPACE_ID`, `CMUX_SURFACE_ID`, or a cmux socket), it:
|
|
278
|
+
|
|
279
|
+
1. Starts a new `lterm` PTY session for the worker command.
|
|
280
|
+
2. Asks cmux to create a native split (`cmux new-split right/down`).
|
|
281
|
+
3. Sends the compatibility command `lterm attach <pane>` into that split, so cmux panes still work if `LTERM_BIN` points at an older `lterm` build that predates `resume`.
|
|
282
|
+
|
|
283
|
+
This gives cmux a real pane to decorate while `lterm` retains scrollback capture and `send-keys` compatibility.
|
|
284
|
+
|
|
285
|
+
**Notifications:**
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
lterm notify --title 'Task complete' --body 'All checks passed'
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
`lterm notify` first tries `cmux notify`. If that's unavailable, it emits OSC 777 so cmux or another compatible terminal can still surface the notification. Notification fields are stripped of terminal control characters before falling back to OSC; subtitle/body separators such as newlines are normalized to spaces rather than concatenated.
|
|
292
|
+
|
|
293
|
+
## Remote access
|
|
294
|
+
|
|
295
|
+
If `lterm` is installed on a remote machine:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
lterm ssh user@host main
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
This uses the same attach-or-create behavior as `lterm open main` on the remote host; the wire command remains `lterm attach-or-new main` so newer local clients still work with older remote `lterm` installs that do not know `open`. Pass SSH flags after `--`:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
lterm ssh devbox main -- -p 2222 -i ~/.ssh/id_ed25519
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Architecture
|
|
308
|
+
|
|
309
|
+
- **Daemon** — one Unix socket per user under `$XDG_RUNTIME_DIR`, with an owner-only fallback under `/tmp`.
|
|
310
|
+
- **PTY sessions** — spawned via `portable-pty`, backed by ring-buffer scrollback.
|
|
311
|
+
- **Attach protocol** — the CLI sends JSON over the Unix socket, optionally reserves the bottom row for a local status bar, then streams PTY bytes.
|
|
312
|
+
- **tmux shim** — a small shell script named `tmux` forwards commands to `lterm tmux-compat`.
|
|
313
|
+
- **cmux bridge** — optional; uses the cmux CLI when detected.
|
|
314
|
+
|
|
315
|
+
After upgrading the `lterm` binary, restart any already-running daemon before
|
|
316
|
+
relying on newly added wire-protocol behavior; existing daemon processes keep
|
|
317
|
+
the old code until they are stopped.
|
|
318
|
+
|
|
319
|
+
## Security notes
|
|
320
|
+
|
|
321
|
+
**Terminal output is forwarded as-is.** `lterm resume` (compatibility name: `lterm attach`) passes PTY bytes through so full-screen terminal programs and cmux/OSC notifications keep working. The local status bar is purely a client-side decoration; use `--no-status` for a fully raw terminal surface. Untrusted child programs can still emit terminal escape sequences to an attached terminal — exactly as under tmux/screen. **Do not use `lterm` as an escape-sequence sanitizer or sandbox.**
|
|
322
|
+
|
|
323
|
+
**Capture output is sanitized for human/AI consumption.** `lterm logs` (compatibility name: `lterm capture`) and `tmux capture-pane` strip common terminal control sequences before printing scrollback.
|
|
324
|
+
|
|
325
|
+
**Process visibility.** `lterm processes [session]` (or compatibility name `lterm ps [session]`) shows the process tree rooted at each session child, so long-running Codex/OMX/MCP subprocess buildup stays visible before it becomes a memory-leak surprise. The system `ps` is invoked by absolute path, and malformed process rows are skipped rather than guessed at.
|
|
326
|
+
|
|
327
|
+
**Socket location.** Custom `LTERM_SOCKET` paths must live in an owner-only directory. Prefer `LTERM_RUNTIME_DIR` when you need an isolated socket location.
|
|
328
|
+
|
|
329
|
+
**Popup commands.** `tmux-compat display-popup` runs the requested command through the user's shell to preserve tmux-like behavior. **Do not pass untrusted popup commands.**
|
|
330
|
+
|
|
331
|
+
**Build reproducibility.** Use the committed lockfile for release builds: `cargo build --release --locked`. The current lockfile pins `serde_json 1.0.149`. Its transitive `zmij` dependency is part of the official serde_json package metadata on docs.rs/crates.io — not a local vendored crate.
|
|
332
|
+
|
|
333
|
+
## Current limitations
|
|
334
|
+
|
|
335
|
+
- Session persistence lasts only while the daemon and host are alive — reboot/process-state restore is not implemented.
|
|
336
|
+
- Outside cmux, `split-window` creates additional managed PTY sessions but does not draw a tiled in-terminal UI.
|
|
337
|
+
- This is a compatibility subset, not a full tmux server. Scripts using advanced tmux formats or options may need additional shim commands.
|
|
338
|
+
- cmux pane capture is handled through `lterm` sessions, not cmux scrollback APIs.
|
|
339
|
+
- The daemon authenticates local clients via OS peer credentials and owner-only socket paths — there are no per-session ACLs yet.
|
|
340
|
+
- Session shutdown uses verified process-group signaling, so child trees like `shell → OMX → Codex → MCP` are cleaned up together when possible. Processes that intentionally detach into a different session/process group can outlive `lterm close` / `lterm kill`; inspect them with `lterm processes` / `lterm ps` or OS process tools.
|
|
341
|
+
|
|
342
|
+
## Development
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
cargo fmt
|
|
346
|
+
cargo test
|
|
347
|
+
cargo build --locked
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Use isolated runtime directories for manual testing:
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
TMP=$(mktemp -d)
|
|
354
|
+
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- start --name test -- sh -lc 'echo hi; sleep 10'
|
|
355
|
+
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- logs test -S=-20
|
|
356
|
+
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- shutdown
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## License
|
|
360
|
+
|
|
361
|
+
Licensed under either of:
|
|
362
|
+
|
|
363
|
+
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
|
|
364
|
+
- MIT License ([LICENSE-MIT](LICENSE-MIT))
|
|
365
|
+
|
|
366
|
+
at your option.
|
package/bin/lterm.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const SUPPORTED = new Map([
|
|
9
|
+
['darwin:arm64', 'lterm-darwin-arm64'],
|
|
10
|
+
['darwin:x64', 'lterm-darwin-x64'],
|
|
11
|
+
['linux:arm64', 'lterm-linux-arm64'],
|
|
12
|
+
['linux:x64', 'lterm-linux-x64'],
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
function isExecutable(file) {
|
|
16
|
+
try {
|
|
17
|
+
fs.accessSync(file, fs.constants.X_OK);
|
|
18
|
+
return true;
|
|
19
|
+
} catch (_) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function packageBinary(packageName) {
|
|
25
|
+
try {
|
|
26
|
+
const packageJson = require.resolve(`${packageName}/package.json`, {
|
|
27
|
+
paths: [__dirname],
|
|
28
|
+
});
|
|
29
|
+
return path.join(path.dirname(packageJson), 'bin', 'lterm');
|
|
30
|
+
} catch (_) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function repoFallbackBinary() {
|
|
36
|
+
return path.resolve(__dirname, '..', 'target', 'release', 'lterm');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveBinary() {
|
|
40
|
+
if (process.env.LTERM_BIN) {
|
|
41
|
+
return process.env.LTERM_BIN;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const key = `${process.platform}:${process.arch}`;
|
|
45
|
+
const packageName = SUPPORTED.get(key);
|
|
46
|
+
if (!packageName) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Unsupported platform ${process.platform}/${process.arch}. ` +
|
|
49
|
+
'Install from source with `cargo install light-terminal` or use Homebrew on supported Unix platforms.'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const fromPackage = packageBinary(packageName);
|
|
54
|
+
if (fromPackage && isExecutable(fromPackage)) {
|
|
55
|
+
return fromPackage;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fallback = repoFallbackBinary();
|
|
59
|
+
if (isExecutable(fallback)) {
|
|
60
|
+
return fallback;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Missing native binary package ${packageName}. ` +
|
|
65
|
+
'Reinstall with optional dependencies enabled, or set LTERM_BIN to a built lterm binary.'
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let binary;
|
|
70
|
+
try {
|
|
71
|
+
binary = resolveBinary();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(`lterm npm wrapper: ${error.message}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
78
|
+
|
|
79
|
+
if (result.error) {
|
|
80
|
+
console.error(`lterm npm wrapper: failed to execute ${binary}: ${result.error.message}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
process.exit(result.status === null ? 1 : result.status);
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ictechgy/lterm",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight tmux-compatible terminal session daemon with cmux-friendly notifications.",
|
|
5
|
+
"license": "MIT OR Apache-2.0",
|
|
6
|
+
"homepage": "https://github.com/ictechgy/light_terminal#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ictechgy/light_terminal.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/ictechgy/light_terminal/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"terminal",
|
|
16
|
+
"tmux",
|
|
17
|
+
"pty",
|
|
18
|
+
"cmux",
|
|
19
|
+
"multiplexer"
|
|
20
|
+
],
|
|
21
|
+
"bin": {
|
|
22
|
+
"lterm": "bin/lterm.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin/lterm.js",
|
|
26
|
+
"README.md",
|
|
27
|
+
"README.ko.md",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"LICENSE-APACHE",
|
|
30
|
+
"LICENSE-MIT"
|
|
31
|
+
],
|
|
32
|
+
"optionalDependencies": {
|
|
33
|
+
"lterm-darwin-arm64": "0.1.0",
|
|
34
|
+
"lterm-darwin-x64": "0.1.0",
|
|
35
|
+
"lterm-linux-arm64": "0.1.0",
|
|
36
|
+
"lterm-linux-x64": "0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=16"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
}
|
|
44
|
+
}
|