@sanohiro/casty 0.5.4 → 0.5.6
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.ja.md +82 -102
- package/README.md +83 -103
- package/bin/casty +6 -0
- package/bin/casty.js +3 -2
- package/lib/browser.js +5 -9
- package/lib/hints.js +7 -3
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -1,47 +1,33 @@
|
|
|
1
1
|
# casty
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ターミナルで本物の Chrome ブラウザを動かす。
|
|
4
4
|
|
|
5
5
|
**[English](README.md)**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
casty は w3m や lynx のようなテキストブラウザではありません。ヘッドレス Chrome を起動し、CDP でレンダリング結果を取得して、Kitty graphics protocol でターミナルに描画します。Chrome のリモートデスクトップがターミナルに収まった感じです。
|
|
8
8
|
|
|
9
|
-
<video src="https://github.com/user-attachments/assets/
|
|
9
|
+
<video src="https://github.com/user-attachments/assets/552f1972-bb53-481e-9516-c36b7e5085d8" autoplay loop muted playsinline></video>
|
|
10
|
+
|
|
11
|
+
## 仕組み
|
|
10
12
|
|
|
11
13
|
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
│
|
|
15
|
-
│
|
|
16
|
-
│
|
|
17
|
-
|
|
14
|
+
ターミナル(あなた) casty Chrome(ヘッドレス)
|
|
15
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
16
|
+
│ Kitty │ ←── │ Screencast │ ←── │ 完全な Web │
|
|
17
|
+
│ graphics │ │ + 高解像度 │ │ レンダリング │
|
|
18
|
+
│ 画面表示 │ │ キャプチャ │ │ JS, CSS, │
|
|
19
|
+
│ │ ──→ │ 入力 │ ──→ │ Canvas, │
|
|
20
|
+
│ マウス/KB │ │ ブリッジ │ │ WebGL │
|
|
21
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
18
22
|
```
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- ヘッドレス Chrome によるフル Web レンダリング (生 CDP、Playwright 不使用)
|
|
23
|
-
- ボット検出回避のステルスパッチ (Google ログイン可能)
|
|
24
|
-
- Kitty graphics protocol による画像表示
|
|
25
|
-
- マウス操作 (クリック、スクロール、ドラッグ)
|
|
26
|
-
- キーボード入力を Chrome にパススルー
|
|
27
|
-
- Vimium 風ヒントモード (Alt+F) でキーボードナビゲーション
|
|
28
|
-
- アドレスバー + 検索 (Alt+L)
|
|
29
|
-
- ブックマーク (アドレスバーで `/b` 検索)
|
|
30
|
-
- テキスト選択コピー / クリップボードペースト
|
|
31
|
-
- ターミナルのフォントサイズに基づく自動ズーム
|
|
32
|
-
- 動的リサイズ (SIGWINCH)
|
|
33
|
-
- キーバインド設定 (`~/.casty/keys.json`)
|
|
34
|
-
- 各種設定 (`~/.casty/config.json`)
|
|
35
|
-
- ファイルダウンロード (`~/Downloads/` に保存)
|
|
36
|
-
- ローディングインジケーター
|
|
37
|
-
- プロファイル自動クリーンアップによる高速起動
|
|
38
|
-
|
|
39
|
-
## 必要環境
|
|
40
|
-
|
|
41
|
-
- **Kitty graphics protocol** 対応ターミナル
|
|
42
|
-
- Node.js >= 18
|
|
24
|
+
レンダリングはすべて Chrome がやります。casty はフレームをターミナルに流して入力を返すだけのブリッジ(約1200行)。Playwright も puppeteer も使わず、WebSocket で生 CDP を叩いています。
|
|
43
25
|
|
|
44
|
-
|
|
26
|
+
本物の Chrome なので JavaScript, CSS, Canvas, WebGL 全部動きます。ステルスパッチで Google ログインも通ります。マウスのクリック、スクロール、ドラッグ、キーボード入力 — 普通のブラウザと同じ操作ができます。
|
|
27
|
+
|
|
28
|
+
## どういうときに使う?
|
|
29
|
+
|
|
30
|
+
SSH でヘッドレスサーバーに入っていて Web ページを確認したいとき、普通は `curl` か `lynx` か X11 転送しかない。casty ならターミナルを離れずに本物のブラウザが使えます。X11 も VNC も Wayland もいらない。Kitty 対応ターミナルさえあれば OK。
|
|
45
31
|
|
|
46
32
|
## インストール
|
|
47
33
|
|
|
@@ -50,128 +36,122 @@ npm install -g @sanohiro/casty
|
|
|
50
36
|
casty
|
|
51
37
|
```
|
|
52
38
|
|
|
53
|
-
|
|
39
|
+
ソースから:
|
|
54
40
|
|
|
55
41
|
```bash
|
|
56
42
|
git clone https://github.com/sanohiro/casty.git
|
|
57
|
-
cd casty
|
|
58
|
-
npm install
|
|
43
|
+
cd casty && npm install
|
|
59
44
|
./bin/casty
|
|
60
45
|
```
|
|
61
46
|
|
|
62
|
-
初回起動時に Chrome Headless Shell が `~/.casty/browsers/`
|
|
47
|
+
初回起動時に Chrome Headless Shell が `~/.casty/browsers/` に自動インストールされます。
|
|
48
|
+
|
|
49
|
+
### 必要環境
|
|
50
|
+
|
|
51
|
+
- **Kitty graphics protocol** 対応ターミナル(動作確認済み: Ghostty, kitty, bcon)
|
|
52
|
+
- Node.js >= 18
|
|
53
|
+
- `unzip`(Chrome 自動インストールに必要)
|
|
63
54
|
|
|
64
55
|
## 使い方
|
|
65
56
|
|
|
66
57
|
```bash
|
|
67
58
|
casty https://google.com
|
|
68
59
|
casty https://youtube.com
|
|
69
|
-
casty # ホームページを開く
|
|
60
|
+
casty # ホームページを開く
|
|
70
61
|
```
|
|
71
62
|
|
|
72
63
|
### キーバインド
|
|
73
64
|
|
|
74
65
|
| キー | アクション |
|
|
75
66
|
|------|-----------|
|
|
76
|
-
| Alt+L |
|
|
77
|
-
| Alt+F |
|
|
78
|
-
| Alt+Left | 戻る |
|
|
79
|
-
| Alt+Right | 進む |
|
|
67
|
+
| Alt+L | アドレスバー |
|
|
68
|
+
| Alt+F | ヒントモード(Vimium 風) |
|
|
69
|
+
| Alt+Left / Right | 戻る / 進む |
|
|
80
70
|
| Alt+C | 選択テキストをコピー |
|
|
81
|
-
| Ctrl+V |
|
|
71
|
+
| Ctrl+V | ペースト |
|
|
82
72
|
| Ctrl+Q | 終了 |
|
|
83
|
-
| Ctrl+C | 終了 (フォールバック) |
|
|
84
|
-
|
|
85
|
-
`~/.casty/keys.json` でカスタマイズ可能 (ファイルは自動生成されません):
|
|
86
|
-
|
|
87
|
-
```json
|
|
88
|
-
{
|
|
89
|
-
"ctrl+q": "quit",
|
|
90
|
-
"alt+left": "back",
|
|
91
|
-
"alt+right": "forward",
|
|
92
|
-
"alt+l": "url_bar",
|
|
93
|
-
"alt+f": "hints",
|
|
94
|
-
"alt+c": "copy",
|
|
95
|
-
"ctrl+v": "paste"
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
73
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- **Alt+L** または1行目クリックでフォーカス — URL が全選択状態になる
|
|
102
|
-
- **Enter** で移動 (URL) または検索 (Google)
|
|
103
|
-
- **`/b クエリ`** でブックマーク検索
|
|
104
|
-
- **Escape** でキャンセル
|
|
105
|
-
- **Ctrl+A** 全選択、**Ctrl+U** 全消去、**Ctrl+W** 単語削除
|
|
74
|
+
`~/.casty/keys.json` でカスタマイズ可能。
|
|
106
75
|
|
|
107
76
|
### ヒントモード
|
|
108
77
|
|
|
109
|
-
**Alt+F**
|
|
78
|
+
**Alt+F** でクリック可能な要素にラベルを表示。ラベルを入力してクリック。ホームロウキー (`a s d f j k l`) を使用。
|
|
110
79
|
|
|
111
|
-
|
|
80
|
+
### アドレスバー
|
|
81
|
+
|
|
82
|
+
**Alt+L** で開く。URL または検索クエリを入力。`/b クエリ` でブックマーク検索。
|
|
112
83
|
|
|
113
84
|
### ブックマーク
|
|
114
85
|
|
|
115
|
-
`~/.casty/bookmarks.json`
|
|
86
|
+
`~/.casty/bookmarks.json` を作成:
|
|
116
87
|
|
|
117
88
|
```json
|
|
118
89
|
{
|
|
119
90
|
"GitHub": "https://github.com",
|
|
120
|
-
"Google": "https://google.com",
|
|
121
91
|
"YouTube": "https://youtube.com"
|
|
122
92
|
}
|
|
123
93
|
```
|
|
124
94
|
|
|
125
|
-
アドレスバーで `/b クエリ` と入力して検索 (名前・URL の部分一致、大文字小文字無視)。
|
|
126
|
-
|
|
127
95
|
### 設定
|
|
128
96
|
|
|
129
|
-
`~/.casty/config.json
|
|
97
|
+
`~/.casty/config.json`:
|
|
130
98
|
|
|
131
99
|
```json
|
|
132
100
|
{
|
|
133
101
|
"homeUrl": "https://github.com/sanohiro/casty",
|
|
134
102
|
"searchUrl": "https://www.google.com/search?q=",
|
|
135
103
|
"transport": "auto",
|
|
136
|
-
"format": "auto"
|
|
104
|
+
"format": "auto",
|
|
105
|
+
"mouseMode": 1002
|
|
137
106
|
}
|
|
138
107
|
```
|
|
139
108
|
|
|
140
109
|
| キー | 説明 | デフォルト |
|
|
141
110
|
|------|------|-----------|
|
|
142
|
-
| `homeUrl` |
|
|
143
|
-
| `searchUrl` | 検索エンジン URL
|
|
144
|
-
| `transport` |
|
|
145
|
-
| `format` |
|
|
146
|
-
|
|
147
|
-
|
|
111
|
+
| `homeUrl` | スタートページ | `https://github.com/sanohiro/casty` |
|
|
112
|
+
| `searchUrl` | 検索エンジン URL | `https://www.google.com/search?q=` |
|
|
113
|
+
| `transport` | 画像転送方式: `auto`, `file`, `inline` | `auto` (bcon/kitty→file、他→inline) |
|
|
114
|
+
| `format` | キャプチャ形式: `auto`, `png`, `jpeg` | `auto` (file→jpeg adaptive、inline→png) |
|
|
115
|
+
| `mouseMode` | `1002` (ボタンイベント) or `1003` (全イベント) | 自動 (Ghostty→1003、他→1002) |
|
|
116
|
+
|
|
117
|
+
## 比較
|
|
118
|
+
|
|
119
|
+
| | casty | Browsh | w3m/lynx |
|
|
120
|
+
|---|---|---|---|
|
|
121
|
+
| エンジン | Chrome | Firefox | 独自パーサー |
|
|
122
|
+
| 描画 | ピクセルそのまま | テキスト近似 | テキストのみ |
|
|
123
|
+
| JavaScript | 動く | 動く | 動かない |
|
|
124
|
+
| 表示方式 | Kitty graphics | 文字セル | 文字セル |
|
|
125
|
+
| 依存 | Node.js + Chrome | Go + Firefox | 単体 |
|
|
126
|
+
|
|
127
|
+
<details>
|
|
128
|
+
<summary>技術詳細</summary>
|
|
129
|
+
|
|
130
|
+
全体で約1200行の JavaScript です。中でやっていること:
|
|
131
|
+
|
|
132
|
+
- chrome-headless-shell を起動して生 CDP WebSocket で通信
|
|
133
|
+
- `Runtime.enable` は絶対に送らない(Google ログインが壊れる。これは苦労して発見した)
|
|
134
|
+
- ステルスパッチは `Page.addScriptToEvaluateOnNewDocument` でページロード前に注入
|
|
135
|
+
- フレーム取得はハイブリッド方式: 低解像度 Screencast で変更を検知して、`Page.captureScreenshot` で DPR 対応の高解像度フレームを取得
|
|
136
|
+
- ファイル転送モードでは JPEG→PNG のアダプティブ切替: スクロール中や動画再生中は高速な JPEG、止まったら鮮明な PNG
|
|
137
|
+
- CSI 14t でターミナルのピクセルサイズを取得して自動ズーム
|
|
148
138
|
|
|
149
139
|
```
|
|
150
|
-
bin/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
lib/
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
keys.js # キーバインド設定
|
|
163
|
-
config.js # ユーザー設定
|
|
140
|
+
bin/casty シェルラッパー(Chrome インストール/更新)
|
|
141
|
+
bin/casty.js エントリポイント(ターミナル、ズーム、リサイズ)
|
|
142
|
+
lib/browser.js CDP ブラウザ制御、フレームキャプチャ
|
|
143
|
+
lib/cdp.js 軽量 CDP WebSocket クライアント
|
|
144
|
+
lib/chrome.js Chrome 検出、起動、プロファイルクリーンアップ
|
|
145
|
+
lib/kitty.js Kitty graphics protocol(file/inline)
|
|
146
|
+
lib/input.js マウス/キーボード処理
|
|
147
|
+
lib/hints.js Vimium 風ヒントモード
|
|
148
|
+
lib/urlbar.js アドレス/検索バー
|
|
149
|
+
lib/config.js ユーザー設定
|
|
150
|
+
lib/keys.js キーバインド設定
|
|
151
|
+
lib/bookmarks.js ブックマーク検索
|
|
164
152
|
```
|
|
165
153
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
1. 生 CDP で Chrome Headless Shell を起動 (Playwright 不使用、`Runtime.enable` も送信しない)
|
|
169
|
-
2. ページロード前にステルスパッチを注入してボット検出を回避
|
|
170
|
-
3. ハイブリッドフレーム取得: 低解像度 Screencast を変更検知トリガーとして使い、`Page.captureScreenshot` で高解像度フレームを取得
|
|
171
|
-
4. Kitty graphics protocol でフレームをターミナルに描画
|
|
172
|
-
5. ターミナル入力 (raw mode) をキャプチャし、CDP 経由で Chrome に送信
|
|
173
|
-
6. CSI 14t でターミナルのピクセルサイズを自動検出し、ズームを計算
|
|
174
|
-
7. 起動時にプロファイルをクリーンアップ (Cookie/LocalStorage を保持、キャッシュ類を削除) して高速起動を維持
|
|
154
|
+
</details>
|
|
175
155
|
|
|
176
156
|
## ライセンス
|
|
177
157
|
|
package/README.md
CHANGED
|
@@ -1,47 +1,33 @@
|
|
|
1
1
|
# casty
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run a real Chrome browser inside your terminal.
|
|
4
4
|
|
|
5
|
-
**[
|
|
5
|
+
**[Japanese](README.ja.md)**
|
|
6
6
|
|
|
7
|
-
casty
|
|
7
|
+
casty is not a text-mode browser like w3m or lynx. It launches headless Chrome, grabs the rendered frames over CDP, and draws them in your terminal via Kitty graphics protocol. Think of it as a remote desktop for Chrome that fits in a terminal window.
|
|
8
8
|
|
|
9
|
-
<video src="https://github.com/user-attachments/assets/
|
|
9
|
+
<video src="https://github.com/user-attachments/assets/552f1972-bb53-481e-9516-c36b7e5085d8" autoplay loop muted playsinline></video>
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
10
12
|
|
|
11
13
|
```
|
|
12
|
-
Chrome (
|
|
13
|
-
|
|
14
|
-
│
|
|
15
|
-
│
|
|
16
|
-
│
|
|
17
|
-
|
|
14
|
+
Terminal (you) casty Chrome (headless)
|
|
15
|
+
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
|
16
|
+
│ Kitty │ ←── │ Screencast │ ←── │ Full web │
|
|
17
|
+
│ graphics │ │ + hi-res │ │ rendering │
|
|
18
|
+
│ display │ │ capture │ │ JS, CSS, │
|
|
19
|
+
│ │ ──→ │ Input │ ──→ │ Canvas, │
|
|
20
|
+
│ Mouse/KB │ │ bridge │ │ WebGL │
|
|
21
|
+
└──────────────┘ └──────────────┘ └──────────────┘
|
|
18
22
|
```
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- Full web rendering via headless Chrome (raw CDP, no Playwright)
|
|
23
|
-
- Stealth patches to avoid bot detection (Google login works)
|
|
24
|
-
- Kitty graphics protocol for image display
|
|
25
|
-
- Mouse support (click, scroll, drag)
|
|
26
|
-
- Keyboard passthrough to Chrome
|
|
27
|
-
- Vimium-style hint mode (Alt+F) for keyboard navigation
|
|
28
|
-
- Address bar with search (Alt+L)
|
|
29
|
-
- Bookmarks (`/b` search in address bar)
|
|
30
|
-
- Copy selected text / paste from clipboard
|
|
31
|
-
- Auto-zoom based on terminal font size
|
|
32
|
-
- Dynamic resize (SIGWINCH)
|
|
33
|
-
- Configurable keybindings (`~/.casty/keys.json`)
|
|
34
|
-
- Configurable settings (`~/.casty/config.json`)
|
|
35
|
-
- File downloads to `~/Downloads/`
|
|
36
|
-
- Loading indicator
|
|
37
|
-
- Fast startup with automatic profile cleanup
|
|
38
|
-
|
|
39
|
-
## Requirements
|
|
40
|
-
|
|
41
|
-
- **Kitty graphics protocol** compatible terminal
|
|
42
|
-
- Node.js >= 18
|
|
24
|
+
Chrome does all the rendering. casty is just a bridge (~1200 lines) that streams frames to your terminal and sends input back. No Playwright, no puppeteer — raw CDP over WebSocket.
|
|
43
25
|
|
|
44
|
-
|
|
26
|
+
Since it's real Chrome, JavaScript, CSS, Canvas, and WebGL all work. Google login works too (stealth patches bypass bot detection). Mouse clicks, scrolling, dragging, typing — everything you'd expect.
|
|
27
|
+
|
|
28
|
+
## Why use this?
|
|
29
|
+
|
|
30
|
+
If you're working over SSH on a headless server and need to check a web page, your options are usually `curl`, `lynx`, or forwarding X11. casty gives you an actual browser without leaving the terminal. No X11, no VNC, no Wayland — just a Kitty-compatible terminal.
|
|
45
31
|
|
|
46
32
|
## Installation
|
|
47
33
|
|
|
@@ -50,128 +36,122 @@ npm install -g @sanohiro/casty
|
|
|
50
36
|
casty
|
|
51
37
|
```
|
|
52
38
|
|
|
53
|
-
Or
|
|
39
|
+
Or from source:
|
|
54
40
|
|
|
55
41
|
```bash
|
|
56
42
|
git clone https://github.com/sanohiro/casty.git
|
|
57
|
-
cd casty
|
|
58
|
-
npm install
|
|
43
|
+
cd casty && npm install
|
|
59
44
|
./bin/casty
|
|
60
45
|
```
|
|
61
46
|
|
|
62
|
-
Chrome Headless Shell is
|
|
47
|
+
Chrome Headless Shell is auto-installed to `~/.casty/browsers/` on first run.
|
|
48
|
+
|
|
49
|
+
### Requirements
|
|
50
|
+
|
|
51
|
+
- A terminal with **Kitty graphics protocol** support (tested on Ghostty, kitty, bcon)
|
|
52
|
+
- Node.js >= 18
|
|
53
|
+
- `unzip` (for Chrome auto-install)
|
|
63
54
|
|
|
64
55
|
## Usage
|
|
65
56
|
|
|
66
57
|
```bash
|
|
67
58
|
casty https://google.com
|
|
68
59
|
casty https://youtube.com
|
|
69
|
-
casty # opens home page
|
|
60
|
+
casty # opens home page
|
|
70
61
|
```
|
|
71
62
|
|
|
72
63
|
### Keybindings
|
|
73
64
|
|
|
74
65
|
| Key | Action |
|
|
75
66
|
|-----|--------|
|
|
76
|
-
| Alt+L |
|
|
77
|
-
| Alt+F | Hint mode (Vimium-style
|
|
78
|
-
| Alt+Left | Back |
|
|
79
|
-
| Alt+Right | Forward |
|
|
67
|
+
| Alt+L | Address bar |
|
|
68
|
+
| Alt+F | Hint mode (Vimium-style) |
|
|
69
|
+
| Alt+Left / Right | Back / Forward |
|
|
80
70
|
| Alt+C | Copy selected text |
|
|
81
|
-
| Ctrl+V | Paste
|
|
71
|
+
| Ctrl+V | Paste |
|
|
82
72
|
| Ctrl+Q | Quit |
|
|
83
|
-
| Ctrl+C | Quit (fallback) |
|
|
84
|
-
|
|
85
|
-
Customize via `~/.casty/keys.json` (file is not created automatically):
|
|
86
|
-
|
|
87
|
-
```json
|
|
88
|
-
{
|
|
89
|
-
"ctrl+q": "quit",
|
|
90
|
-
"alt+left": "back",
|
|
91
|
-
"alt+right": "forward",
|
|
92
|
-
"alt+l": "url_bar",
|
|
93
|
-
"alt+f": "hints",
|
|
94
|
-
"alt+c": "copy",
|
|
95
|
-
"ctrl+v": "paste"
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
73
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- **Alt+L** or click row 1 to focus — URL is selected, type to replace
|
|
102
|
-
- **Enter** to navigate (URLs) or search (Google)
|
|
103
|
-
- **`/b query`** to search bookmarks
|
|
104
|
-
- **Escape** to cancel
|
|
105
|
-
- **Ctrl+A** select all, **Ctrl+U** clear, **Ctrl+W** delete word
|
|
74
|
+
Customizable via `~/.casty/keys.json`.
|
|
106
75
|
|
|
107
76
|
### Hint Mode
|
|
108
77
|
|
|
109
|
-
|
|
78
|
+
**Alt+F** shows labels on clickable elements. Type the label to click. Labels use home-row keys (`a s d f j k l`).
|
|
110
79
|
|
|
111
|
-
|
|
80
|
+
### Address Bar
|
|
81
|
+
|
|
82
|
+
**Alt+L** to open. Type a URL or search query. `/b query` searches bookmarks.
|
|
112
83
|
|
|
113
84
|
### Bookmarks
|
|
114
85
|
|
|
115
|
-
Create `~/.casty/bookmarks.json
|
|
86
|
+
Create `~/.casty/bookmarks.json`:
|
|
116
87
|
|
|
117
88
|
```json
|
|
118
89
|
{
|
|
119
90
|
"GitHub": "https://github.com",
|
|
120
|
-
"Google": "https://google.com",
|
|
121
91
|
"YouTube": "https://youtube.com"
|
|
122
92
|
}
|
|
123
93
|
```
|
|
124
94
|
|
|
125
|
-
Search from the address bar with `/b query` (matches name or URL, case-insensitive).
|
|
126
|
-
|
|
127
95
|
### Configuration
|
|
128
96
|
|
|
129
|
-
|
|
97
|
+
`~/.casty/config.json`:
|
|
130
98
|
|
|
131
99
|
```json
|
|
132
100
|
{
|
|
133
101
|
"homeUrl": "https://github.com/sanohiro/casty",
|
|
134
102
|
"searchUrl": "https://www.google.com/search?q=",
|
|
135
103
|
"transport": "auto",
|
|
136
|
-
"format": "auto"
|
|
104
|
+
"format": "auto",
|
|
105
|
+
"mouseMode": 1002
|
|
137
106
|
}
|
|
138
107
|
```
|
|
139
108
|
|
|
140
109
|
| Key | Description | Default |
|
|
141
110
|
|-----|-------------|---------|
|
|
142
|
-
| `homeUrl` |
|
|
143
|
-
| `searchUrl` | Search engine URL
|
|
144
|
-
| `transport` |
|
|
145
|
-
| `format` |
|
|
146
|
-
|
|
147
|
-
|
|
111
|
+
| `homeUrl` | Start page | `https://github.com/sanohiro/casty` |
|
|
112
|
+
| `searchUrl` | Search engine URL | `https://www.google.com/search?q=` |
|
|
113
|
+
| `transport` | Image transfer: `auto`, `file`, `inline` | `auto` (bcon/kitty→file, others→inline) |
|
|
114
|
+
| `format` | Capture format: `auto`, `png`, `jpeg` | `auto` (file→jpeg adaptive, inline→png) |
|
|
115
|
+
| `mouseMode` | `1002` (button-event) or `1003` (any-event) | Auto (Ghostty→1003, others→1002) |
|
|
116
|
+
|
|
117
|
+
## Comparison
|
|
118
|
+
|
|
119
|
+
| | casty | Browsh | w3m/lynx |
|
|
120
|
+
|---|---|---|---|
|
|
121
|
+
| Engine | Chrome | Firefox | Custom parser |
|
|
122
|
+
| Rendering | Pixel-perfect | Text approximation | Text only |
|
|
123
|
+
| JavaScript | Yes | Yes | No |
|
|
124
|
+
| Display | Kitty graphics | Character cells | Character cells |
|
|
125
|
+
| Dependencies | Node.js + Chrome | Go + Firefox | Standalone |
|
|
126
|
+
|
|
127
|
+
<details>
|
|
128
|
+
<summary>Technical Details</summary>
|
|
129
|
+
|
|
130
|
+
The whole thing is about 1200 lines of JavaScript. Here's what's going on under the hood:
|
|
131
|
+
|
|
132
|
+
- Launches chrome-headless-shell and talks to it via raw CDP WebSocket
|
|
133
|
+
- `Runtime.enable` is never sent (it breaks Google login — discovered the hard way)
|
|
134
|
+
- Stealth patches are injected via `Page.addScriptToEvaluateOnNewDocument` before any page loads
|
|
135
|
+
- Frame capture is hybrid: low-res Screencast triggers change detection, then `Page.captureScreenshot` grabs hi-res frames with proper DPR
|
|
136
|
+
- File transfer mode uses adaptive JPEG→PNG: fast JPEG during scrolling/video, crisp PNG after things settle
|
|
137
|
+
- Terminal pixel size is detected via CSI 14t for auto-zoom
|
|
148
138
|
|
|
149
139
|
```
|
|
150
|
-
bin/
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
lib/
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
keys.js # Configurable keybindings
|
|
163
|
-
config.js # User configuration
|
|
140
|
+
bin/casty Shell wrapper (Chrome install/update)
|
|
141
|
+
bin/casty.js Entry point (terminal, zoom, resize)
|
|
142
|
+
lib/browser.js CDP browser control, frame capture
|
|
143
|
+
lib/cdp.js Lightweight CDP WebSocket client
|
|
144
|
+
lib/chrome.js Chrome detection, launch, profile cleanup
|
|
145
|
+
lib/kitty.js Kitty graphics protocol (file/inline)
|
|
146
|
+
lib/input.js Mouse/keyboard handling
|
|
147
|
+
lib/hints.js Vimium-style hint mode
|
|
148
|
+
lib/urlbar.js Address/search bar
|
|
149
|
+
lib/config.js User configuration
|
|
150
|
+
lib/keys.js Keybinding config
|
|
151
|
+
lib/bookmarks.js Bookmark search
|
|
164
152
|
```
|
|
165
153
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
1. Launches Chrome Headless Shell via raw CDP (no Playwright, no `Runtime.enable`)
|
|
169
|
-
2. Injects stealth patches before page load to avoid bot detection
|
|
170
|
-
3. Uses hybrid frame capture: low-res Screencast as change detection trigger, `Page.captureScreenshot` for high-res frames
|
|
171
|
-
4. Renders frames to terminal via Kitty graphics protocol
|
|
172
|
-
5. Captures terminal input (raw mode) and dispatches to Chrome via CDP
|
|
173
|
-
6. Auto-detects terminal pixel size (CSI 14t) for zoom calculation
|
|
174
|
-
7. Cleans up profile on startup (keeps cookies/storage, removes caches) for fast launch
|
|
154
|
+
</details>
|
|
175
155
|
|
|
176
156
|
## License
|
|
177
157
|
|
package/bin/casty
CHANGED
|
@@ -163,6 +163,12 @@ has_system_chrome() {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
if ! has_chrome; then
|
|
166
|
+
# unzip が必要 (Chrome for Testing は zip 配布)
|
|
167
|
+
if ! is_arm64_linux && ! command -v unzip >/dev/null 2>&1; then
|
|
168
|
+
echo "casty: 'unzip' is required to install Chrome Headless Shell." >&2
|
|
169
|
+
echo "casty: Install with: sudo apt install unzip" >&2
|
|
170
|
+
exit 1
|
|
171
|
+
fi
|
|
166
172
|
if is_arm64_linux; then
|
|
167
173
|
# ARM64 Linux: Chrome for Testing にバイナリがないので Playwright 経由でインストール
|
|
168
174
|
install_chromium_playwright
|
package/bin/casty.js
CHANGED
|
@@ -149,10 +149,11 @@ async function main() {
|
|
|
149
149
|
|
|
150
150
|
// Frame callback for screencast / captureScreenshot
|
|
151
151
|
// sendFrame includes cursor positioning (single write)
|
|
152
|
+
let urlBar = null;
|
|
152
153
|
function onFrame(data) {
|
|
153
154
|
if (renderPaused) return;
|
|
154
155
|
sendFrame(data);
|
|
155
|
-
urlBar.renderIfDirty();
|
|
156
|
+
if (urlBar) urlBar.renderIfDirty();
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
// Phase 3: Start screencast
|
|
@@ -163,7 +164,7 @@ async function main() {
|
|
|
163
164
|
onFrame,
|
|
164
165
|
});
|
|
165
166
|
|
|
166
|
-
|
|
167
|
+
urlBar = startInputHandling(client, cssCellW, cssCellH, bindings, pauseRender, forceCapture);
|
|
167
168
|
urlBar.render();
|
|
168
169
|
|
|
169
170
|
// Force capture on page load events (debounced — multiple events fire close together)
|
package/lib/browser.js
CHANGED
|
@@ -11,10 +11,6 @@ const CHROME_VERSION = '145.0.7632.6';
|
|
|
11
11
|
const USER_AGENT = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${CHROME_VERSION} Safari/537.36`;
|
|
12
12
|
const PROFILE_DIR = join(homedir(), '.casty', 'profile');
|
|
13
13
|
|
|
14
|
-
// WEBGL_debug_renderer_info extension constants
|
|
15
|
-
const GL_UNMASKED_VENDOR = 0x9245; // UNMASKED_VENDOR_WEBGL
|
|
16
|
-
const GL_UNMASKED_RENDERER = 0x9246; // UNMASKED_RENDERER_WEBGL
|
|
17
|
-
|
|
18
14
|
// Screencast settings (low-res stream for change detection)
|
|
19
15
|
const SCREENCAST_SCALE = 4; // Downscale factor (1/4 resolution)
|
|
20
16
|
const SCREENCAST_NTH_FRAME = 1; // Every frame (no decimation, faster change detection)
|
|
@@ -27,7 +23,6 @@ const CAPTURE_STUCK_RESET = 5000; // Reset stuck capturing flag (ms)
|
|
|
27
23
|
// lang: primary language (e.g. "ja", "en-US")
|
|
28
24
|
function buildStealthScript(lang) {
|
|
29
25
|
// Build Accept-Language style list: primary, then en-US/en fallbacks
|
|
30
|
-
const langBase = lang.split('-')[0]; // "ja", "en", "zh", etc.
|
|
31
26
|
const languages = [lang];
|
|
32
27
|
if (lang !== 'en-US' && lang !== 'en') {
|
|
33
28
|
languages.push('en-US', 'en');
|
|
@@ -118,10 +113,11 @@ function buildAcceptLanguage(lang) {
|
|
|
118
113
|
|
|
119
114
|
// HTTP GET → JSON (using global fetch)
|
|
120
115
|
async function fetchJson(url) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
116
|
+
try {
|
|
117
|
+
const res = await fetch(url);
|
|
118
|
+
if (!res.ok) return null;
|
|
119
|
+
return await res.json();
|
|
120
|
+
} catch { return null; }
|
|
125
121
|
}
|
|
126
122
|
|
|
127
123
|
// Phase 1: Launch Chrome only (no terminal info needed, for parallel execution)
|
package/lib/hints.js
CHANGED
|
@@ -18,9 +18,13 @@ function makeCollectAndOverlayScript(hintChars, maxHints) {
|
|
|
18
18
|
const elems = [];
|
|
19
19
|
for (const el of all) {
|
|
20
20
|
if (elems.length >= ${maxHints}) break;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (rect.width === 0 || rect.height === 0)
|
|
21
|
+
let rect = el.getBoundingClientRect();
|
|
22
|
+
// Handle display:contents — element has no box, use first child rect
|
|
23
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
24
|
+
const child = el.firstElementChild;
|
|
25
|
+
if (child) rect = child.getBoundingClientRect();
|
|
26
|
+
if (rect.width === 0 || rect.height === 0) continue;
|
|
27
|
+
}
|
|
24
28
|
if (rect.bottom < 0 || rect.top > window.innerHeight) continue;
|
|
25
29
|
if (rect.right < 0 || rect.left > window.innerWidth) continue;
|
|
26
30
|
|