@ictechgy/lterm 1.0.8 → 1.0.10
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.ko.md +43 -8
- package/README.md +26 -11
- package/bin/lterm.js +19 -1
- package/package.json +11 -6
- package/scripts/validate_npm_packages.mjs +127 -0
package/README.ko.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Light Terminal (`lterm`)
|
|
2
2
|
|
|
3
|
-
한국어 | [English](README.md)
|
|
3
|
+
한국어 | [English](README.md) | 🌐 [브라우저용 HTML 가이드](https://ictechgy.github.io/light_terminal/)
|
|
4
4
|
|
|
5
5
|
## TL;DR
|
|
6
6
|
|
|
@@ -29,8 +29,9 @@ agent가 보통 필요로 하는 더 작은 표면에 집중합니다.
|
|
|
29
29
|
CLI, OpenCode, GitHub Copilot CLI, Cursor Agent, Antigravity/`agy`, Kiro, Jules, Aider, Goose, Amp, Crush, Kimi, Qwen, Gemini CLI, OMX/OMC 같은 terminal-first tooling이 쓰는 tmux command
|
|
30
30
|
subset을 구현합니다.
|
|
31
31
|
- **Raw attach, safe reports** — attach된 PTY stream은 TUI/interactive shell을
|
|
32
|
-
위해 raw로
|
|
33
|
-
|
|
32
|
+
위해 raw로 유지합니다. 대신 `logs`, `capture`, `compose`, `doctor`,
|
|
33
|
+
`diagnose`, `notify` fallback 경로 같은 report surface는 terminal
|
|
34
|
+
control을 제거하면서도 한국어, CJK, emoji 같은 UTF-8 text는 보존합니다.
|
|
34
35
|
- **cmux-friendly 설계** — notification과 tmux shim call이 generic desktop
|
|
35
36
|
multiplexer보다 cmux/agent pane orchestration에 맞춰져 있습니다.
|
|
36
37
|
- **내장 관측성** — `doctor` / `status`, bounded `logs --start/--end`,
|
|
@@ -75,7 +76,7 @@ GitHub에서 Cargo로 설치할 때는 release tag를 고정하세요. 아래
|
|
|
75
76
|
README 릴리스 기준이며, 더 최신 tag가 있는지는 Releases 페이지에서 확인하세요:
|
|
76
77
|
|
|
77
78
|
```bash
|
|
78
|
-
cargo install --locked --git https://github.com/ictechgy/light_terminal --tag v1.0.
|
|
79
|
+
cargo install --locked --git https://github.com/ictechgy/light_terminal --tag v1.0.10
|
|
79
80
|
```
|
|
80
81
|
|
|
81
82
|
저장소를 클론한 뒤 직접 빌드하려면 Rust 1.85 이상이 필요합니다.
|
|
@@ -137,6 +138,7 @@ lterm -a api
|
|
|
137
138
|
| 세션 status theme 설정 | `lterm status-theme api green` | `theme` |
|
|
138
139
|
| 정제된 scrollback 읽기 | `lterm logs api --start=-80 --end=-1` | `capture` |
|
|
139
140
|
| 디버깅용 raw PTY 출력 기록 | `lterm trace api --duration 5s --output trace.jsonl` | `record` |
|
|
141
|
+
| 신뢰하는 raw PTY trace 재생 | `lterm trace-replay trace.jsonl` | `replay-trace` |
|
|
140
142
|
| 정제된 scrollback 위에 입력 컴포저 열기 | `lterm compose api` | `mobile` |
|
|
141
143
|
| 세션 출력 또는 종료 대기 | `lterm wait api --contains READY --timeout 30s --json` | 없음 |
|
|
142
144
|
| 세션을 감시하고 완료 시 알림 | `lterm watch api --exit --notify` | 없음 |
|
|
@@ -191,11 +193,23 @@ escape 처리가 여기에 포함됩니다. 직접 `ssh`하듯 신뢰할 수 있
|
|
|
191
193
|
|
|
192
194
|
`lterm doctor`(호환 이름: `lterm status`)는 client/daemon version, protocol 호환성, runtime/data/socket/shim path, shim directory가 `PATH`에 있는지 등을 보고합니다. 이 명령은 daemon을 시작하지 않습니다. 현재 socket에서 호환 daemon이 응답하지 않으면 `daemon_reachable=no` / `false`로 표시됩니다. 일반 client 동작 중 접근 가능한 daemon이 다른 lterm 또는 protocol version을 보고하면 stderr에 경고를 출력하며, 보통 binary upgrade 뒤 예전 daemon이 살아 있는 상황을 뜻합니다.
|
|
193
195
|
|
|
194
|
-
`lterm logs <target>`은 `--start` / `-S`와 `--end` / `-E` line offset을 받습니다. 0 이상의 값은 absolute scrollback line index이고, 음수 값은 현재 scrollback line count에서 뒤로 셉니다. `--end`는 inclusive라 `lterm logs api -S0 -E0`은 첫 번째 줄만 capture합니다. Capture 출력은 계속 정제된 text
|
|
196
|
+
`lterm logs <target>`은 `--start` / `-S`와 `--end` / `-E` line offset을 받습니다. 0 이상의 값은 absolute scrollback line index이고, 음수 값은 현재 scrollback line count에서 뒤로 셉니다. `--end`는 inclusive라 `lterm logs api -S0 -E0`은 첫 번째 줄만 capture합니다. Capture 출력은 계속 정제된 text입니다. 즉 terminal control은 제거하고 한국어, CJK, emoji 같은 UTF-8 text는 보존합니다. attach된 PTY stream은 raw 그대로 유지됩니다.
|
|
197
|
+
|
|
198
|
+
`lterm trace <target> --duration 5s --output trace.jsonl`은 timestamp와
|
|
199
|
+
hex-encoded bytes를 담은 private local JSONL 파일로 raw PTY 출력 chunk를
|
|
200
|
+
기록합니다. 간헐적인 render 문제를 opt-in으로 디버깅하기 위한 기능이며,
|
|
201
|
+
recorder는 JSONL artifact만 쓰고 raw capture는 `--max-bytes`(기본 16 MiB)
|
|
202
|
+
한도 안에서 멈춥니다. 기존 trace file은 `--force` 없이는 덮어쓰지 않습니다.
|
|
203
|
+
재생은 신뢰할 수 있는 trace에만 `lterm trace-replay <file>`로 수행하세요.
|
|
204
|
+
`trace-replay`는 raw terminal bytes를 출력하기 전에 JSONL 전체를 먼저
|
|
205
|
+
검증하고, 기본 trace capture 크기와 trace당 chunk 수 한도를 적용합니다.
|
|
195
206
|
|
|
196
207
|
`lterm wait <target> --exit / --contains <text>`는 세션이 종료되거나 정제된 scrollback에 marker가 나타날 때까지 block합니다. 자동화용 health check에는 `--timeout 250ms|2s|5m|1h`, `--tail N`, `--json`을 함께 쓰세요. Timeout 시 `wait` / `watch`는 exit code `124`를 반환하고 JSON에는 `timed_out: true`가 들어갑니다. `lterm watch`는 같은 조건을 쓰며, `--notify`를 더하면 attach된 PTY bytes는 건드리지 않고 cmux-friendly 완료 알림을 보냅니다. `--json`을 함께 쓸 때는 notification fallback이 필요해도 stdout을 machine-readable JSON으로 유지합니다.
|
|
208
|
+
지나치게 큰 `--contains` needle은 명시적 오류로 거부되며, daemon은 동시에
|
|
209
|
+
block 중인 `wait` / `watch` check 수를 제한해 자동화가 무제한 waiter를
|
|
210
|
+
만들지 못하게 합니다.
|
|
197
211
|
|
|
198
|
-
`LTERM_STATUS_STYLE=full` 또는 `LTERM_STATUS_STYLE=minimal` 로 시각 스타일을 선택할 수 있습니다. `full`(로컬 터미널 기본값)은 색이 있는 bar를 그리고, `minimal`은 SGR 색을 모두 생략한 plain text로 동작합니다. SSH 세션(`SSH_CONNECTION` / `SSH_CLIENT` / `SSH_TTY` 감지)에서는 자동으로 `minimal`이 적용되어
|
|
212
|
+
`LTERM_STATUS_STYLE=full` 또는 `LTERM_STATUS_STYLE=minimal` 로 시각 스타일을 선택할 수 있습니다. `full`(로컬 터미널 기본값)은 색이 있는 bar를 그리고, `minimal`은 SGR 색을 모두 생략한 plain text로 동작합니다. SSH 세션(`SSH_CONNECTION` / `SSH_CLIENT` / `SSH_TTY` 감지)과 Termius 계열 클라이언트(터미널 식별 환경 변수 감지)에서는 자동으로 `minimal`이 적용되어 모바일 색상 매핑 문제를 줄이지만, 세션 또는 환경 theme을 명시하면 색이 유지됩니다.
|
|
199
213
|
|
|
200
214
|
`LTERM_STATUS_THEME=blue|green|magenta|cyan|amber|red|gray|plain` 으로 attach client의 기본 status bar 색을 바꿀 수 있습니다. 세션별 override가 환경값보다 우선합니다: `lterm start --status-theme amber -n api -- npm run dev`, `lterm run --status-color cyan -- cargo test`, `lterm status-theme api plain`. 이 변수를 shell startup 파일에서 export하면 SSH attach도 colored status bar로 opt-in됩니다. 모바일 SSH client에서 plain text가 필요하면 unset하거나 `LTERM_STATUS_STYLE=minimal`을 설정하세요. Theme 이름은 고정 allowlist에서만 파싱되며, lterm은 사용자 입력 escape sequence를 status row에 임의 삽입하지 않습니다.
|
|
201
215
|
|
|
@@ -403,7 +417,7 @@ tmux `-f` filter는 조용히 무시하지 않고 의도적으로 거부합니
|
|
|
403
417
|
|
|
404
418
|
1. worker 명령을 위한 새 `lterm` PTY 세션을 시작합니다.
|
|
405
419
|
2. cmux에 native split 생성을 요청합니다 (`cmux new-split right/down`).
|
|
406
|
-
3.
|
|
420
|
+
3. 생성된 split에는 호환 명령인 `lterm attach <pane>`을 보냅니다. 안전한 absolute executable이 `LTERM_BIN`으로 지정되어 있으면 그 값을 사용하고, 아니면 현재 executable로 fallback합니다. 이 호환 명령 덕분에 `resume`을 모르는 구버전 build에서도 cmux pane이 계속 동작합니다.
|
|
407
421
|
|
|
408
422
|
이렇게 하면 실제 pane은 cmux가 그리고, scrollback capture와 `send-keys` 호환은 `lterm`이 유지합니다.
|
|
409
423
|
|
|
@@ -447,12 +461,19 @@ lterm ssh devbox main -- -p 2222 -i ~/.ssh/id_ed25519
|
|
|
447
461
|
|
|
448
462
|
**터미널 출력은 그대로 전달됩니다.** `lterm resume`(호환 이름: `lterm attach`)은 full-screen 터미널 프로그램과 cmux/OSC 알림이 정상 동작하도록 PTY byte를 그대로 통과시킵니다. 로컬 상태 바는 클라이언트 쪽 표시 요소일 뿐이며, 완전한 raw 모드 터미널이 필요하면 `--no-status`를 사용하세요. 신뢰할 수 없는 child 프로그램은 tmux/screen에서와 마찬가지로 attach된 터미널에 escape sequence를 출력할 수 있습니다. **`lterm`을 escape-sequence sanitizer나 sandbox로 사용하지 마세요.**
|
|
449
463
|
|
|
450
|
-
**Capture 출력은 표시/로깅 전에 terminal control sequence를 제거합니다.** `lterm logs`(호환 이름: `lterm capture`), `lterm compose`(alias: `lterm mobile`), `tmux capture-pane`은 captured scrollback을 출력할 때
|
|
464
|
+
**Capture 출력은 표시/로깅 전에 terminal control sequence를 제거합니다.** `lterm logs`(호환 이름: `lterm capture`), `lterm compose`(alias: `lterm mobile`), `tmux capture-pane`은 captured scrollback을 출력할 때 raw 또는 UTF-8 encoded C1 control을 포함한 터미널 제어 시퀀스를 제거합니다. 정상 UTF-8 text인 한국어, CJK, emoji는 보존합니다. 그래도 scrollback text는 신뢰할 수 없는 프로그램 출력일 수 있으므로 사람이나 agent에게 넘기기 전에 확인하세요. `compose`는 attach가 아닌 view에서 기존 input/send 경로로 텍스트를 commit하며, raw attached PTY stream을 변환하지 않습니다.
|
|
451
465
|
|
|
452
466
|
**프로세스 가시성.** `lterm processes [session]`(호환 이름: `lterm ps [session]`)은 process-group id와 함께 각 세션 child 아래의 process tree를 보여 줍니다. `--orphans`를 추가하면 기록된 session root의 descendant가 아니지만 같은 process group에 남아 있는 row도 함께 보여 주므로, Codex/OMX/MCP subprocess가 누적되어 메모리 누수처럼 커지기 전에 확인할 수 있습니다. 시스템 `ps`는 절대 경로로 호출하며, 형식이 잘못된 process row는 추측하지 않고 건너뜁니다.
|
|
453
467
|
|
|
454
468
|
**Socket 위치.** 커스텀 `LTERM_SOCKET` 경로는 소유자 전용 디렉터리 안에 있어야 합니다. 격리된 socket 위치가 필요할 때는 `LTERM_RUNTIME_DIR`를 우선 사용하세요.
|
|
455
469
|
|
|
470
|
+
**Binary override.** `LTERM_BIN`은 cmux split attach command처럼 child
|
|
471
|
+
`lterm`을 다시 호출할 때 쓰는 신뢰된 개발자용 override입니다. Absolute
|
|
472
|
+
path이고, control character가 없고, 실행 가능한 regular file일 때만
|
|
473
|
+
override로 인정합니다. 유효하지 않은 값은 무시하고 현재 executable 또는
|
|
474
|
+
`PATH`의 `lterm`으로 fallback합니다. 신뢰할 수 없는 environment data에서
|
|
475
|
+
`LTERM_BIN`을 설정하지 마세요.
|
|
476
|
+
|
|
456
477
|
**Popup 명령.** `tmux-compat display-popup`은 tmux와 비슷한 동작을 위해 요청된 명령을 사용자 shell로 실행합니다. **신뢰할 수 없는 popup 명령을 전달하지 마세요.**
|
|
457
478
|
|
|
458
479
|
**빌드 재현성.** 릴리스 빌드는 커밋된 lockfile을 사용하세요: `cargo build --release --locked`. 현재 lockfile은 `serde_json 1.0.149`와 registry dependency인 `zmij 1.0.21`을 고정합니다. 둘 다 Cargo가 lockfile에 따라 resolve하는 crate이며, 로컬에 vendor된 crate가 아닙니다. `cargo tree --locked -p serde_json`으로 의존성 집합을 확인할 수 있습니다.
|
|
@@ -483,6 +504,20 @@ LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- logs test -
|
|
|
483
504
|
LTERM_RUNTIME_DIR="$TMP/run" LTERM_DATA_DIR="$TMP/data" cargo run -- shutdown
|
|
484
505
|
```
|
|
485
506
|
|
|
507
|
+
릴리스/계약 preflight 헬퍼:
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
scripts/release-preflight.sh --contract-only
|
|
511
|
+
scripts/release-preflight.sh --allow-occupied-skip --skip-audit
|
|
512
|
+
scripts/dependency-minor-dry-run.sh
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
`scripts/release-preflight.sh`의 `--run-soak`은 manual release-gate soak
|
|
516
|
+
profile에서만 사용하세요. Tagging 또는 publishing 전에 release, audit,
|
|
517
|
+
contract, dependency, soak evidence를 남길 때는
|
|
518
|
+
[`docs/release-evidence-template.md`](docs/release-evidence-template.md)를
|
|
519
|
+
사용하세요.
|
|
520
|
+
|
|
486
521
|
## 라이선스
|
|
487
522
|
|
|
488
523
|
다음 두 라이선스 중 하나를 선택해 사용할 수 있습니다.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Light Terminal (`lterm`)
|
|
2
2
|
|
|
3
|
-
[한국어](README.ko.md) | English
|
|
3
|
+
[한국어](README.ko.md) | English | 🌐 [Browser-friendly HTML guide](https://ictechgy.github.io/light_terminal/)
|
|
4
4
|
|
|
5
5
|
## TL;DR
|
|
6
6
|
|
|
@@ -30,8 +30,9 @@ need:
|
|
|
30
30
|
CLI, Cursor Agent, Antigravity/`agy`, Kiro, Jules, Aider, Goose, Amp,
|
|
31
31
|
Crush, Kimi, Qwen, Gemini CLI, OMX/OMC, and similar terminal-first tooling.
|
|
32
32
|
- **Raw attach, safe reports** — attached PTY streams remain raw for TUIs and
|
|
33
|
-
interactive shells, while `logs`, `capture`, `
|
|
34
|
-
|
|
33
|
+
interactive shells, while `logs`, `capture`, `compose`, `doctor`,
|
|
34
|
+
`diagnose`, the `notify` fallback path, and other report surfaces strip
|
|
35
|
+
terminal controls while preserving UTF-8 text such as Korean, CJK, and emoji.
|
|
35
36
|
- **cmux-friendly by design** — notifications and tmux shim calls are shaped for
|
|
36
37
|
cmux/agent pane orchestration instead of generic desktop multiplexing.
|
|
37
38
|
- **Built-in observability** — `doctor` / `status`, bounded `logs --start/--end`,
|
|
@@ -78,7 +79,7 @@ With Cargo from GitHub, pin a release tag. The example below uses the current
|
|
|
78
79
|
README release; check the Releases page for newer tags:
|
|
79
80
|
|
|
80
81
|
```bash
|
|
81
|
-
cargo install --locked --git https://github.com/ictechgy/light_terminal --tag v1.0.
|
|
82
|
+
cargo install --locked --git https://github.com/ictechgy/light_terminal --tag v1.0.10
|
|
82
83
|
```
|
|
83
84
|
|
|
84
85
|
Building from this checkout requires Rust 1.85 or newer:
|
|
@@ -142,6 +143,7 @@ lterm -a api
|
|
|
142
143
|
| Set a session status theme | `lterm status-theme api green` | `theme` |
|
|
143
144
|
| Read sanitized scrollback | `lterm logs api --start=-80 --end=-1` | `capture` |
|
|
144
145
|
| Record raw PTY output for debugging | `lterm trace api --duration 5s --output trace.jsonl` | `record` |
|
|
146
|
+
| Replay a trusted raw PTY trace | `lterm trace-replay trace.jsonl` | `replay-trace` |
|
|
145
147
|
| Open a sanitized scrollback composer for input | `lterm compose api` | `mobile` |
|
|
146
148
|
| Wait for session output or exit | `lterm wait api --contains READY --timeout 30s --json` | None |
|
|
147
149
|
| Watch a session and notify on completion | `lterm watch api --exit --notify` | None |
|
|
@@ -201,17 +203,24 @@ flags, and — only when an existing daemon is reachable — session metadata pl
|
|
|
201
203
|
process rows. It does not start the daemon and does not include raw PTY bytes or
|
|
202
204
|
scrollback by default.
|
|
203
205
|
|
|
204
|
-
`lterm logs <target>` accepts `--start` / `-S` and `--end` / `-E` line offsets. Non-negative values are absolute scrollback line indexes; negative values count back from the current scrollback line count. `--end` is inclusive, so `lterm logs api -S0 -E0` captures only the first line. Capture output remains sanitized text
|
|
206
|
+
`lterm logs <target>` accepts `--start` / `-S` and `--end` / `-E` line offsets. Non-negative values are absolute scrollback line indexes; negative values count back from the current scrollback line count. `--end` is inclusive, so `lterm logs api -S0 -E0` captures only the first line. Capture output remains sanitized text: terminal controls are removed, while UTF-8 text such as Korean, CJK, and emoji is preserved. Attached PTY streams remain raw.
|
|
205
207
|
|
|
206
208
|
`lterm trace <target> --duration 5s --output trace.jsonl` records raw PTY
|
|
207
209
|
output chunks to a private local JSONL file with timestamps and hex-encoded
|
|
208
|
-
bytes. It is for opt-in debugging of intermittent render issues;
|
|
209
|
-
|
|
210
|
-
refuses to overwrite existing trace files unless `--force` is
|
|
210
|
+
bytes. It is for opt-in debugging of intermittent render issues; the recorder
|
|
211
|
+
only writes the JSONL artifact, caps raw capture at `--max-bytes` (default
|
|
212
|
+
16 MiB), and refuses to overwrite existing trace files unless `--force` is
|
|
213
|
+
passed. To replay, use `lterm trace-replay <file>` only with traces you trust:
|
|
214
|
+
it validates the whole JSONL file before emitting any raw terminal bytes and
|
|
215
|
+
caps replay at the default trace capture size plus a per-trace chunk-count
|
|
216
|
+
limit.
|
|
211
217
|
|
|
212
218
|
`lterm wait <target> --exit / --contains <text>` blocks until a session exits or its sanitized scrollback contains a marker. Add `--timeout 250ms|2s|5m|1h`, `--tail N`, and `--json` for automation-friendly health checks. On timeout, `wait` / `watch` return exit code `124` and JSON reports `timed_out: true`. `lterm watch` uses the same conditions and can add `--notify` to emit a cmux-friendly completion notification without altering attached PTY bytes; with `--json`, stdout stays machine-readable even when notification fallback is needed.
|
|
219
|
+
Oversized `--contains` needles are rejected with an explicit error, and the
|
|
220
|
+
daemon caps concurrent blocking `wait` / `watch` checks so automation cannot fan
|
|
221
|
+
out unbounded waiters.
|
|
213
222
|
|
|
214
|
-
Set `LTERM_STATUS_STYLE=full` or `LTERM_STATUS_STYLE=minimal` to choose the visual style. `full` (default for local terminals) draws a colored bar; `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
|
|
223
|
+
Set `LTERM_STATUS_STYLE=full` or `LTERM_STATUS_STYLE=minimal` to choose the visual style. `full` (default for local terminals) draws a colored bar; `minimal` drops all SGR colors in favor of plain text. SSH sessions (detected via `SSH_CONNECTION`, `SSH_CLIENT`, or `SSH_TTY`) and Termius-style clients (detected via terminal identity variables) default to `minimal` to avoid mobile color-mapping issues, unless a session or environment theme is explicitly set.
|
|
215
224
|
|
|
216
225
|
Set `LTERM_STATUS_THEME=blue|green|magenta|cyan|amber|red|gray|plain` to change the default colored status bar for the attaching client. Per-session overrides win over the environment: `lterm start --status-theme amber -n api -- npm run dev`, `lterm run --status-color cyan -- cargo test`, or `lterm status-theme api plain`. If you export this variable from shell startup files, it also opts SSH attaches into colored status bars; leave it unset or set `LTERM_STATUS_STYLE=minimal` on mobile SSH clients that need plain text. Theme names are parsed from a fixed allowlist; lterm never injects arbitrary user-provided terminal escape sequences into the status row.
|
|
217
226
|
|
|
@@ -416,7 +425,7 @@ When `lterm tmux-compat split-window` detects cmux (via `CMUX_WORKSPACE_ID`, `CM
|
|
|
416
425
|
|
|
417
426
|
1. Starts a new `lterm` PTY session for the worker command.
|
|
418
427
|
2. Asks cmux to create a native split (`cmux new-split right/down`).
|
|
419
|
-
3. Sends the compatibility command `lterm attach <pane>` into that split
|
|
428
|
+
3. Sends the compatibility command `lterm attach <pane>` into that split. If a safe absolute executable is supplied through `LTERM_BIN`, lterm uses it; otherwise it falls back to the current executable. The compatibility command keeps cmux panes working even with older builds that predate `resume`.
|
|
420
429
|
|
|
421
430
|
This gives cmux a real pane to decorate while `lterm` retains scrollback capture and `send-keys` compatibility.
|
|
422
431
|
|
|
@@ -460,12 +469,18 @@ the old code until they are stopped.
|
|
|
460
469
|
|
|
461
470
|
**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.**
|
|
462
471
|
|
|
463
|
-
**Capture output is terminal-control-sanitized before display/logging.** `lterm logs` (compatibility name: `lterm capture`), `lterm compose` (alias: `lterm mobile`), and `tmux capture-pane` strip
|
|
472
|
+
**Capture output is terminal-control-sanitized before display/logging.** `lterm logs` (compatibility name: `lterm capture`), `lterm compose` (alias: `lterm mobile`), and `tmux capture-pane` strip terminal control sequences, including raw or UTF-8-encoded C1 controls, before printing scrollback. Valid UTF-8 text such as Korean, CJK, and emoji is preserved. Scrollback text can still be untrusted program output, so review it before feeding it to humans or agents. `compose` is a non-attached view that commits text through the existing input/send path; it does not transform raw attached PTY streams.
|
|
464
473
|
|
|
465
474
|
**Process visibility.** `lterm processes [session]` (or compatibility name `lterm ps [session]`) shows the process tree rooted at each session child, including process-group ids. Add `--orphans` to also include same-process-group rows that are no longer descendants of the recorded session root, 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.
|
|
466
475
|
|
|
467
476
|
**Socket location.** Custom `LTERM_SOCKET` paths must live in an owner-only directory. Prefer `LTERM_RUNTIME_DIR` when you need an isolated socket location.
|
|
468
477
|
|
|
478
|
+
**Binary override.** `LTERM_BIN` is a trusted developer override for child
|
|
479
|
+
`lterm` invocations such as cmux split attach commands. Overrides are accepted
|
|
480
|
+
only when they are absolute paths to executable regular files and contain no
|
|
481
|
+
control characters; invalid values are ignored in favor of the current
|
|
482
|
+
executable or `lterm` on `PATH`. Do not set it from untrusted environment data.
|
|
483
|
+
|
|
469
484
|
**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.**
|
|
470
485
|
|
|
471
486
|
**Build reproducibility.** Use the committed lockfile for release builds: `cargo build --release --locked`. The current lockfile pins `serde_json 1.0.149` and its registry dependency `zmij 1.0.21`; both are resolved by Cargo from the lockfile, not vendored locally. Verify the dependency set with `cargo tree --locked -p serde_json`.
|
package/bin/lterm.js
CHANGED
|
@@ -15,6 +15,9 @@ const SUPPORTED = new Map([
|
|
|
15
15
|
|
|
16
16
|
function isExecutable(file) {
|
|
17
17
|
try {
|
|
18
|
+
if (!fs.statSync(file).isFile()) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
18
21
|
fs.accessSync(file, fs.constants.X_OK);
|
|
19
22
|
return true;
|
|
20
23
|
} catch (_) {
|
|
@@ -22,6 +25,21 @@ function isExecutable(file) {
|
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
function hasControlChars(value) {
|
|
29
|
+
return /[\x00-\x1f\x7f-\x9f]/.test(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function overrideBinary(value) {
|
|
33
|
+
if (hasControlChars(value)) {
|
|
34
|
+
throw new Error('LTERM_BIN must not contain control characters.');
|
|
35
|
+
}
|
|
36
|
+
const resolved = path.resolve(value);
|
|
37
|
+
if (!isExecutable(resolved)) {
|
|
38
|
+
throw new Error(`LTERM_BIN is not executable: ${resolved}`);
|
|
39
|
+
}
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
function packageBinary(packageName) {
|
|
26
44
|
try {
|
|
27
45
|
const packageJson = require.resolve(`${packageName}/package.json`, {
|
|
@@ -39,7 +57,7 @@ function repoFallbackBinary() {
|
|
|
39
57
|
|
|
40
58
|
function resolveBinary() {
|
|
41
59
|
if (process.env.LTERM_BIN) {
|
|
42
|
-
return process.env.LTERM_BIN;
|
|
60
|
+
return overrideBinary(process.env.LTERM_BIN);
|
|
43
61
|
}
|
|
44
62
|
|
|
45
63
|
const key = `${process.platform}:${process.arch}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ictechgy/lterm",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Lightweight tmux-compatible terminal session daemon with cmux-friendly notifications.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/ictechgy/light_terminal#readme",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"bin": {
|
|
22
22
|
"lterm": "bin/lterm.js"
|
|
23
23
|
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"prepublishOnly": "node scripts/validate_npm_packages.mjs",
|
|
26
|
+
"validate:npm-packages": "node scripts/validate_npm_packages.mjs"
|
|
27
|
+
},
|
|
24
28
|
"files": [
|
|
25
29
|
"bin/lterm.js",
|
|
26
30
|
"docs/assets/lterm-demo.svg",
|
|
@@ -28,13 +32,14 @@
|
|
|
28
32
|
"README.ko.md",
|
|
29
33
|
"LICENSE",
|
|
30
34
|
"LICENSE-APACHE",
|
|
31
|
-
"LICENSE-MIT"
|
|
35
|
+
"LICENSE-MIT",
|
|
36
|
+
"scripts/validate_npm_packages.mjs"
|
|
32
37
|
],
|
|
33
38
|
"optionalDependencies": {
|
|
34
|
-
"lterm-darwin-arm64": "1.0.
|
|
35
|
-
"lterm-darwin-x64": "1.0.
|
|
36
|
-
"lterm-linux-arm64": "1.0.
|
|
37
|
-
"lterm-linux-x64": "1.0.
|
|
39
|
+
"lterm-darwin-arm64": "1.0.10",
|
|
40
|
+
"lterm-darwin-x64": "1.0.10",
|
|
41
|
+
"lterm-linux-arm64": "1.0.10",
|
|
42
|
+
"lterm-linux-x64": "1.0.10"
|
|
38
43
|
},
|
|
39
44
|
"engines": {
|
|
40
45
|
"node": ">=16"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const root = path.resolve(__dirname, '..');
|
|
10
|
+
const requireBinaries = process.argv.includes('--require-binaries');
|
|
11
|
+
const platforms = [
|
|
12
|
+
{
|
|
13
|
+
name: 'lterm-darwin-arm64',
|
|
14
|
+
dir: 'npm/platforms/lterm-darwin-arm64',
|
|
15
|
+
os: 'darwin',
|
|
16
|
+
cpu: 'arm64',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'lterm-darwin-x64',
|
|
20
|
+
dir: 'npm/platforms/lterm-darwin-x64',
|
|
21
|
+
os: 'darwin',
|
|
22
|
+
cpu: 'x64',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'lterm-linux-arm64',
|
|
26
|
+
dir: 'npm/platforms/lterm-linux-arm64',
|
|
27
|
+
os: 'linux',
|
|
28
|
+
cpu: 'arm64',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'lterm-linux-x64',
|
|
32
|
+
dir: 'npm/platforms/lterm-linux-x64',
|
|
33
|
+
os: 'linux',
|
|
34
|
+
cpu: 'x64',
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const rootScripts = {
|
|
38
|
+
prepublishOnly: 'node scripts/validate_npm_packages.mjs',
|
|
39
|
+
'validate:npm-packages': 'node scripts/validate_npm_packages.mjs',
|
|
40
|
+
};
|
|
41
|
+
const forbiddenPlatformFields = [
|
|
42
|
+
'scripts',
|
|
43
|
+
'dependencies',
|
|
44
|
+
'devDependencies',
|
|
45
|
+
'optionalDependencies',
|
|
46
|
+
'peerDependencies',
|
|
47
|
+
'bundledDependencies',
|
|
48
|
+
'bundleDependencies',
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function readJson(relativePath) {
|
|
52
|
+
return JSON.parse(fs.readFileSync(path.join(root, relativePath), 'utf8'));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function fail(message) {
|
|
56
|
+
console.error(`npm package validation failed: ${message}`);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function sameArray(actual, expected) {
|
|
61
|
+
return (
|
|
62
|
+
Array.isArray(actual) &&
|
|
63
|
+
actual.length === expected.length &&
|
|
64
|
+
expected.every((value, index) => actual[index] === value)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sameObject(actual, expected) {
|
|
69
|
+
return JSON.stringify(actual || {}) === JSON.stringify(expected);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const rootPackage = readJson('package.json');
|
|
73
|
+
const version = rootPackage.version;
|
|
74
|
+
const optionalDeps = rootPackage.optionalDependencies || {};
|
|
75
|
+
const expectedOptionalDeps = Object.fromEntries(platforms.map((platform) => [platform.name, version]));
|
|
76
|
+
|
|
77
|
+
if (!sameObject(rootPackage.scripts, rootScripts)) {
|
|
78
|
+
fail(`root scripts ${JSON.stringify(rootPackage.scripts || {})} do not match expected publish guards`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const actualKeys = Object.keys(optionalDeps).sort();
|
|
82
|
+
const expectedKeys = Object.keys(expectedOptionalDeps).sort();
|
|
83
|
+
if (JSON.stringify(actualKeys) !== JSON.stringify(expectedKeys)) {
|
|
84
|
+
fail(`root optionalDependencies keys ${JSON.stringify(actualKeys)} do not match ${JSON.stringify(expectedKeys)}`);
|
|
85
|
+
}
|
|
86
|
+
for (const [name, expectedVersion] of Object.entries(expectedOptionalDeps)) {
|
|
87
|
+
if (optionalDeps[name] !== expectedVersion) {
|
|
88
|
+
fail(`root optionalDependency ${name}@${optionalDeps[name]} does not match root version ${expectedVersion}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const platform of platforms) {
|
|
93
|
+
const pkg = readJson(`${platform.dir}/package.json`);
|
|
94
|
+
if (pkg.name !== platform.name) {
|
|
95
|
+
fail(`${platform.dir} package name ${pkg.name} does not match ${platform.name}`);
|
|
96
|
+
}
|
|
97
|
+
if (pkg.version !== version) {
|
|
98
|
+
fail(`${platform.name} version ${pkg.version} does not match root version ${version}`);
|
|
99
|
+
}
|
|
100
|
+
if (!sameArray(pkg.os, [platform.os])) {
|
|
101
|
+
fail(`${platform.name} os ${JSON.stringify(pkg.os)} does not match [${platform.os}]`);
|
|
102
|
+
}
|
|
103
|
+
if (!sameArray(pkg.cpu, [platform.cpu])) {
|
|
104
|
+
fail(`${platform.name} cpu ${JSON.stringify(pkg.cpu)} does not match [${platform.cpu}]`);
|
|
105
|
+
}
|
|
106
|
+
if (!sameArray(pkg.files, ['bin/lterm'])) {
|
|
107
|
+
fail(`${platform.name} files ${JSON.stringify(pkg.files)} does not exactly publish bin/lterm`);
|
|
108
|
+
}
|
|
109
|
+
for (const field of forbiddenPlatformFields) {
|
|
110
|
+
if (Object.prototype.hasOwnProperty.call(pkg, field)) {
|
|
111
|
+
fail(`${platform.name} must not declare ${field}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (requireBinaries) {
|
|
115
|
+
const binary = path.join(root, platform.dir, 'bin', 'lterm');
|
|
116
|
+
try {
|
|
117
|
+
fs.accessSync(binary, fs.constants.X_OK);
|
|
118
|
+
} catch (_) {
|
|
119
|
+
fail(`${platform.name} missing executable ${path.relative(root, binary)}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (process.exitCode) {
|
|
125
|
+
process.exit(process.exitCode);
|
|
126
|
+
}
|
|
127
|
+
console.log(`npm package metadata ok for ${rootPackage.name}@${version}`);
|