@sanohiro/casty 0.5.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 +21 -0
- package/README.ja.md +178 -0
- package/README.md +178 -0
- package/bin/casty +200 -0
- package/bin/casty.js +245 -0
- package/lib/bookmarks.js +32 -0
- package/lib/browser.js +305 -0
- package/lib/cdp.js +76 -0
- package/lib/chrome.js +155 -0
- package/lib/config.js +43 -0
- package/lib/hints.js +255 -0
- package/lib/input.js +545 -0
- package/lib/keys.js +51 -0
- package/lib/kitty.js +117 -0
- package/lib/urlbar.js +348 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hironobu Sano
|
|
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.ja.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# casty
|
|
2
|
+
|
|
3
|
+
Kitty graphics protocol を使った TTY Web ブラウザ。
|
|
4
|
+
|
|
5
|
+
**[English](README.md)**
|
|
6
|
+
|
|
7
|
+
ヘッドレス Chrome のレンダリングを Kitty 対応ターミナルに表示し、ターミナル上で完全な Web ブラウジングを実現します。
|
|
8
|
+
|
|
9
|
+
<video src="https://github.com/user-attachments/assets/330bf0c3-dd08-44a5-b627-b90c200d57fe" autoplay loop muted playsinline></video>
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Chrome (Headless Shell) casty Terminal
|
|
13
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
14
|
+
│ Web レンダリング │ ───→ │ 高解像度 │ ─→│ Kitty graphics │
|
|
15
|
+
│ JS 実行 │ │ キャプチャ │ │ 画面表示 │
|
|
16
|
+
│ フルブラウザ │ ←─── │ 入力ブリッジ │ ←─│ マウス/キーボード│
|
|
17
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
18
|
+
```
|
|
19
|
+
|
|
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 >= 22
|
|
43
|
+
|
|
44
|
+
動作確認済み: **bcon**, **Ghostty**, **kitty**
|
|
45
|
+
|
|
46
|
+
## インストール
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g @sanohiro/casty
|
|
50
|
+
casty
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
ソースからインストールする場合:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/sanohiro/casty.git
|
|
57
|
+
cd casty
|
|
58
|
+
npm install
|
|
59
|
+
./bin/casty
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
初回起動時に Chrome Headless Shell が `~/.casty/browsers/` に自動インストールされます。以降の起動時にバックグラウンドで更新チェックし、常に一世代だけ保持します。
|
|
63
|
+
|
|
64
|
+
## 使い方
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
casty https://google.com
|
|
68
|
+
casty https://youtube.com
|
|
69
|
+
casty # ホームページを開く (デフォルト: casty GitHub ページ)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### キーバインド
|
|
73
|
+
|
|
74
|
+
| キー | アクション |
|
|
75
|
+
|------|-----------|
|
|
76
|
+
| Alt+L | アドレスバーを開く |
|
|
77
|
+
| Alt+F | ヒントモード (Vimium 風リンク/ボタン選択) |
|
|
78
|
+
| Alt+Left | 戻る |
|
|
79
|
+
| Alt+Right | 進む |
|
|
80
|
+
| Alt+C | 選択テキストをコピー |
|
|
81
|
+
| Ctrl+V | クリップボードからペースト |
|
|
82
|
+
| 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
|
+
|
|
99
|
+
### アドレスバー
|
|
100
|
+
|
|
101
|
+
- **Alt+L** または1行目クリックでフォーカス — URL が全選択状態になる
|
|
102
|
+
- **Enter** で移動 (URL) または検索 (Google)
|
|
103
|
+
- **`/b クエリ`** でブックマーク検索
|
|
104
|
+
- **Escape** でキャンセル
|
|
105
|
+
- **Ctrl+A** 全選択、**Ctrl+U** 全消去、**Ctrl+W** 単語削除
|
|
106
|
+
|
|
107
|
+
### ヒントモード
|
|
108
|
+
|
|
109
|
+
**Alt+F** でクリック可能/フォーカス可能な要素にラベルを表示。ラベルの文字を入力するとリンク/ボタンのクリックや入力欄へのフォーカスができます。**Escape** でキャンセル。
|
|
110
|
+
|
|
111
|
+
ラベルはホームロウキー (`a`, `s`, `d`, `f`, `j`, `k`, `l`) を使用 — 7個以下なら1文字、それ以上は2文字 (最大49個)。
|
|
112
|
+
|
|
113
|
+
### ブックマーク
|
|
114
|
+
|
|
115
|
+
`~/.casty/bookmarks.json` を手動で作成:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"GitHub": "https://github.com",
|
|
120
|
+
"Google": "https://google.com",
|
|
121
|
+
"YouTube": "https://youtube.com"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
アドレスバーで `/b クエリ` と入力して検索 (名前・URL の部分一致、大文字小文字無視)。
|
|
126
|
+
|
|
127
|
+
### 設定
|
|
128
|
+
|
|
129
|
+
`~/.casty/config.json` でカスタマイズ可能 (ファイルは自動生成されません):
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"homeUrl": "https://github.com/sanohiro/casty",
|
|
134
|
+
"searchUrl": "https://www.google.com/search?q=",
|
|
135
|
+
"transport": "auto",
|
|
136
|
+
"format": "auto"
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| キー | 説明 | デフォルト |
|
|
141
|
+
|------|------|-----------|
|
|
142
|
+
| `homeUrl` | URL 未指定時に開くページ | `https://github.com/sanohiro/casty` |
|
|
143
|
+
| `searchUrl` | 検索エンジン URL (クエリが末尾に付加される) | `https://www.google.com/search?q=` |
|
|
144
|
+
| `transport` | Kitty 画像転送方式: `auto`, `file`, `inline` | `auto` (bcon→file、他→inline) |
|
|
145
|
+
| `format` | スクリーンショット形式: `auto`, `png`, `jpeg` | `auto` (file→jpeg、inline→png) |
|
|
146
|
+
|
|
147
|
+
## アーキテクチャ
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
bin/
|
|
151
|
+
casty # シェルラッパー (Chrome インストール/更新)
|
|
152
|
+
casty.js # エントリポイント (ターミナル検出、ズーム、リサイズ)
|
|
153
|
+
lib/
|
|
154
|
+
browser.js # CDP ブラウザ制御 (起動、Screencast、キャプチャ)
|
|
155
|
+
cdp.js # 軽量 CDP WebSocket クライアント
|
|
156
|
+
chrome.js # Chrome バイナリ検出、起動、プロファイルクリーンアップ
|
|
157
|
+
kitty.js # Kitty graphics protocol 出力 (file/inline)
|
|
158
|
+
input.js # マウス/キーボード処理、アクション
|
|
159
|
+
hints.js # Vimium 風ヒントモード
|
|
160
|
+
urlbar.js # アドレス/検索バー
|
|
161
|
+
bookmarks.js # ブックマーク検索
|
|
162
|
+
keys.js # キーバインド設定
|
|
163
|
+
config.js # ユーザー設定
|
|
164
|
+
```
|
|
165
|
+
|
|
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 を保持、キャッシュ類を削除) して高速起動を維持
|
|
175
|
+
|
|
176
|
+
## ライセンス
|
|
177
|
+
|
|
178
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# casty
|
|
2
|
+
|
|
3
|
+
A TTY web browser powered by Kitty graphics protocol.
|
|
4
|
+
|
|
5
|
+
**[日本語](README.ja.md)**
|
|
6
|
+
|
|
7
|
+
casty renders full web pages in your terminal using Chrome's headless rendering, bridging the gap between a headless browser and your Kitty-compatible terminal.
|
|
8
|
+
|
|
9
|
+
<video src="https://github.com/user-attachments/assets/330bf0c3-dd08-44a5-b627-b90c200d57fe" autoplay loop muted playsinline></video>
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Chrome (Headless Shell) casty Terminal
|
|
13
|
+
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
14
|
+
│ Web rendering │ ───→ │ High-res │ ─→│ Kitty graphics │
|
|
15
|
+
│ JS execution │ │ capture │ │ display │
|
|
16
|
+
│ Full browser │ ←─── │ Input bridge │ ←─│ Mouse/Keyboard │
|
|
17
|
+
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Features
|
|
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 >= 22
|
|
43
|
+
|
|
44
|
+
Tested on: **bcon**, **Ghostty**, **kitty**
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g @sanohiro/casty
|
|
50
|
+
casty
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or install from source:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/sanohiro/casty.git
|
|
57
|
+
cd casty
|
|
58
|
+
npm install
|
|
59
|
+
./bin/casty
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Chrome Headless Shell is automatically installed to `~/.casty/browsers/` on first run and kept up to date on subsequent launches. Only one version is kept at a time.
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
casty https://google.com
|
|
68
|
+
casty https://youtube.com
|
|
69
|
+
casty # opens home page (default: casty GitHub page)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Keybindings
|
|
73
|
+
|
|
74
|
+
| Key | Action |
|
|
75
|
+
|-----|--------|
|
|
76
|
+
| Alt+L | Open address bar |
|
|
77
|
+
| Alt+F | Hint mode (Vimium-style link/button selection) |
|
|
78
|
+
| Alt+Left | Back |
|
|
79
|
+
| Alt+Right | Forward |
|
|
80
|
+
| Alt+C | Copy selected text |
|
|
81
|
+
| Ctrl+V | Paste from clipboard |
|
|
82
|
+
| 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
|
+
|
|
99
|
+
### Address Bar
|
|
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
|
|
106
|
+
|
|
107
|
+
### Hint Mode
|
|
108
|
+
|
|
109
|
+
Press **Alt+F** to show labels on clickable and focusable elements. Type the label characters to click a link/button or focus an input field. Press **Escape** to cancel.
|
|
110
|
+
|
|
111
|
+
Labels use home-row keys (`a`, `s`, `d`, `f`, `j`, `k`, `l`) — single character for ≤7 elements, two characters for more (up to 49).
|
|
112
|
+
|
|
113
|
+
### Bookmarks
|
|
114
|
+
|
|
115
|
+
Create `~/.casty/bookmarks.json` manually:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"GitHub": "https://github.com",
|
|
120
|
+
"Google": "https://google.com",
|
|
121
|
+
"YouTube": "https://youtube.com"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Search from the address bar with `/b query` (matches name or URL, case-insensitive).
|
|
126
|
+
|
|
127
|
+
### Configuration
|
|
128
|
+
|
|
129
|
+
Customize via `~/.casty/config.json` (file is not created automatically):
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"homeUrl": "https://github.com/sanohiro/casty",
|
|
134
|
+
"searchUrl": "https://www.google.com/search?q=",
|
|
135
|
+
"transport": "auto",
|
|
136
|
+
"format": "auto"
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| Key | Description | Default |
|
|
141
|
+
|-----|-------------|---------|
|
|
142
|
+
| `homeUrl` | Page opened when no URL is given | `https://github.com/sanohiro/casty` |
|
|
143
|
+
| `searchUrl` | Search engine URL (query appended) | `https://www.google.com/search?q=` |
|
|
144
|
+
| `transport` | Kitty image transfer: `auto`, `file`, or `inline` | `auto` (bcon→file, others→inline) |
|
|
145
|
+
| `format` | Screenshot format: `auto`, `png`, or `jpeg` | `auto` (file→jpeg, inline→png) |
|
|
146
|
+
|
|
147
|
+
## Architecture
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
bin/
|
|
151
|
+
casty # Shell wrapper (Chrome install/update)
|
|
152
|
+
casty.js # Entry point (terminal detection, zoom, resize)
|
|
153
|
+
lib/
|
|
154
|
+
browser.js # CDP browser control (launch, screencast, capture)
|
|
155
|
+
cdp.js # Lightweight CDP WebSocket client
|
|
156
|
+
chrome.js # Chrome binary detection, launch, profile cleanup
|
|
157
|
+
kitty.js # Kitty graphics protocol output (file/inline)
|
|
158
|
+
input.js # Mouse/keyboard handling, actions
|
|
159
|
+
hints.js # Vimium-style hint mode
|
|
160
|
+
urlbar.js # Address/search bar
|
|
161
|
+
bookmarks.js # Bookmark search
|
|
162
|
+
keys.js # Configurable keybindings
|
|
163
|
+
config.js # User configuration
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## How It Works
|
|
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
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
package/bin/casty
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# casty - TTY web browser
|
|
3
|
+
# シェルラッパー: Chrome for Testing 管理 + 本体起動
|
|
4
|
+
|
|
5
|
+
# シンボリックリンクを解決して実際のパスを取得
|
|
6
|
+
SOURCE="${BASH_SOURCE[0]}"
|
|
7
|
+
while [ -L "$SOURCE" ]; do
|
|
8
|
+
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
|
|
9
|
+
SOURCE="$(readlink "$SOURCE")"
|
|
10
|
+
[[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
|
|
11
|
+
done
|
|
12
|
+
CASTY_DIR="$(cd -P "$(dirname "$SOURCE")/.." && pwd)"
|
|
13
|
+
|
|
14
|
+
BROWSERS_DIR="$HOME/.casty/browsers"
|
|
15
|
+
STAMP="$HOME/.casty/.update-check"
|
|
16
|
+
|
|
17
|
+
# プラットフォーム自動検出
|
|
18
|
+
detect_platform() {
|
|
19
|
+
local os=$(uname -s)
|
|
20
|
+
local arch=$(uname -m)
|
|
21
|
+
case "$os" in
|
|
22
|
+
Linux)
|
|
23
|
+
case "$arch" in
|
|
24
|
+
aarch64|arm64) echo "linux-arm64" ;; # Chrome for Testing にはないが検出用
|
|
25
|
+
*) echo "linux64" ;;
|
|
26
|
+
esac ;;
|
|
27
|
+
Darwin)
|
|
28
|
+
case "$arch" in
|
|
29
|
+
arm64) echo "mac-arm64" ;;
|
|
30
|
+
*) echo "mac-x64" ;;
|
|
31
|
+
esac ;;
|
|
32
|
+
*) echo "linux64" ;;
|
|
33
|
+
esac
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# ARM64 Linux か判定 (Chrome for Testing にはバイナリがない)
|
|
37
|
+
is_arm64_linux() {
|
|
38
|
+
[ "$(detect_platform)" = "linux-arm64" ]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Playwright 経由で chromium-headless-shell をインストール (ARM64 Linux 用)
|
|
42
|
+
install_chromium_playwright() {
|
|
43
|
+
echo "casty: Installing Chrome Headless Shell via Playwright..." >&2
|
|
44
|
+
if PLAYWRIGHT_BROWSERS_PATH="$BROWSERS_DIR" npx -y playwright install chromium-headless-shell 2>&1 >&2; then
|
|
45
|
+
touch "$STAMP"
|
|
46
|
+
# 古いバージョンを削除 (最新1つだけ残す)
|
|
47
|
+
local dirs=$(ls -t -d "$BROWSERS_DIR"/chromium_headless_shell-*/ 2>/dev/null)
|
|
48
|
+
local count=$(echo "$dirs" | wc -l)
|
|
49
|
+
if [ "$count" -gt 1 ]; then
|
|
50
|
+
echo "$dirs" | tail -n +2 | while read -r d; do rm -rf "$d"; done
|
|
51
|
+
fi
|
|
52
|
+
# Playwright が入れる ffmpeg も古いのを削除
|
|
53
|
+
local ffdirs=$(ls -t -d "$BROWSERS_DIR"/ffmpeg-*/ 2>/dev/null)
|
|
54
|
+
local ffcount=$(echo "$ffdirs" | wc -l)
|
|
55
|
+
if [ "$ffcount" -gt 1 ]; then
|
|
56
|
+
echo "$ffdirs" | tail -n +2 | while read -r d; do rm -rf "$d"; done
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Chrome for Testing API から chrome-headless-shell をダウンロード・インストール
|
|
62
|
+
install_chromium() {
|
|
63
|
+
local platform=$(detect_platform)
|
|
64
|
+
local json_url="https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json"
|
|
65
|
+
|
|
66
|
+
# JSON API から最新バージョンの URL を取得
|
|
67
|
+
local json=$(curl -fsSL "$json_url" 2>/dev/null)
|
|
68
|
+
if [ -z "$json" ]; then
|
|
69
|
+
echo "casty: Failed to fetch Chrome version info" >&2
|
|
70
|
+
return 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# jq がなくても動くよう node で JSON パース
|
|
74
|
+
local download_url=$(node -e "
|
|
75
|
+
const d = JSON.parse(process.argv[1]);
|
|
76
|
+
const hs = d.channels.Stable.downloads['chrome-headless-shell'];
|
|
77
|
+
const e = hs && hs.find(x => x.platform === '$platform');
|
|
78
|
+
if (e) console.log(e.url);
|
|
79
|
+
" "$json" 2>/dev/null)
|
|
80
|
+
|
|
81
|
+
if [ -z "$download_url" ]; then
|
|
82
|
+
echo "casty: No chrome-headless-shell for $platform" >&2
|
|
83
|
+
return 1
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# バージョン番号を抽出
|
|
87
|
+
local version=$(node -e "
|
|
88
|
+
const d = JSON.parse(process.argv[1]);
|
|
89
|
+
console.log(d.channels.Stable.version);
|
|
90
|
+
" "$json" 2>/dev/null)
|
|
91
|
+
|
|
92
|
+
local dest_dir="$BROWSERS_DIR/chrome-headless-shell-${version}"
|
|
93
|
+
|
|
94
|
+
# 既にインストール済みならスキップ
|
|
95
|
+
if [ -f "$dest_dir/chrome-headless-shell" ]; then
|
|
96
|
+
touch "$STAMP"
|
|
97
|
+
return 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
echo "casty: Installing Chrome Headless Shell ${version}..." >&2
|
|
101
|
+
|
|
102
|
+
local tmp_zip=$(mktemp /tmp/casty-chrome-XXXXXX.zip)
|
|
103
|
+
# プログレスバー付きダウンロード (TTY ならバー表示、パイプなら非表示)
|
|
104
|
+
if [ -t 2 ]; then
|
|
105
|
+
curl -fL --progress-bar "$download_url" -o "$tmp_zip" 2>&1 >&2
|
|
106
|
+
else
|
|
107
|
+
curl -fsSL "$download_url" -o "$tmp_zip" 2>/dev/null
|
|
108
|
+
fi
|
|
109
|
+
if [ $? -ne 0 ]; then
|
|
110
|
+
rm -f "$tmp_zip"
|
|
111
|
+
echo "casty: Download failed" >&2
|
|
112
|
+
return 1
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
mkdir -p "$BROWSERS_DIR"
|
|
116
|
+
# zip 内は chrome-headless-shell-<platform>/ ディレクトリに入っている
|
|
117
|
+
local tmp_dir=$(mktemp -d /tmp/casty-extract-XXXXXX)
|
|
118
|
+
unzip -q "$tmp_zip" -d "$tmp_dir" 2>/dev/null
|
|
119
|
+
rm -f "$tmp_zip"
|
|
120
|
+
|
|
121
|
+
# 展開されたディレクトリを見つけてリネーム
|
|
122
|
+
local extracted=$(ls -d "$tmp_dir"/chrome-headless-shell-* 2>/dev/null | head -1)
|
|
123
|
+
if [ -z "$extracted" ] || [ ! -f "$extracted/chrome-headless-shell" ]; then
|
|
124
|
+
rm -rf "$tmp_dir"
|
|
125
|
+
echo "casty: Extraction failed" >&2
|
|
126
|
+
return 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
mv "$extracted" "$dest_dir"
|
|
130
|
+
rm -rf "$tmp_dir"
|
|
131
|
+
chmod +x "$dest_dir/chrome-headless-shell"
|
|
132
|
+
|
|
133
|
+
touch "$STAMP"
|
|
134
|
+
echo "casty: Installed Chrome Headless Shell ${version}" >&2
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# 古いブラウザバージョンを削除 (最新1つだけ残す)
|
|
138
|
+
cleanup_old_browsers() {
|
|
139
|
+
local count=$(ls -d "$BROWSERS_DIR"/chrome-headless-shell-*/ 2>/dev/null | wc -l)
|
|
140
|
+
[ "$count" -le 1 ] && return
|
|
141
|
+
# 最も古いディレクトリを削除
|
|
142
|
+
local oldest=$(ls -t -d "$BROWSERS_DIR"/chrome-headless-shell-*/ 2>/dev/null | tail -1)
|
|
143
|
+
[ -n "$oldest" ] && rm -rf "$oldest"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# 初回: ブラウザがなければインストール (ブロッキング)
|
|
147
|
+
has_chrome() {
|
|
148
|
+
# Chrome for Testing 形式 (ARM64 Linux はスキップ — x86_64 バイナリなので動かない)
|
|
149
|
+
if ! is_arm64_linux; then
|
|
150
|
+
ls "$BROWSERS_DIR"/chrome-headless-shell-*/chrome-headless-shell &>/dev/null && return 0
|
|
151
|
+
fi
|
|
152
|
+
# Playwright 形式 — 再帰的に探索 (バイナリ名: chrome-headless-shell or headless_shell)
|
|
153
|
+
find "$BROWSERS_DIR"/chromium_headless_shell-* \( -name 'chrome-headless-shell' -o -name 'headless_shell' \) -type f 2>/dev/null | grep -q . && return 0
|
|
154
|
+
return 1
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# システム Chrome/Chromium があるか
|
|
158
|
+
has_system_chrome() {
|
|
159
|
+
command -v chromium-browser >/dev/null 2>&1 || \
|
|
160
|
+
command -v chromium >/dev/null 2>&1 || \
|
|
161
|
+
command -v google-chrome-stable >/dev/null 2>&1 || \
|
|
162
|
+
command -v google-chrome >/dev/null 2>&1
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if ! has_chrome; then
|
|
166
|
+
if is_arm64_linux; then
|
|
167
|
+
# ARM64 Linux: Chrome for Testing にバイナリがないので Playwright 経由でインストール
|
|
168
|
+
install_chromium_playwright
|
|
169
|
+
else
|
|
170
|
+
install_chromium
|
|
171
|
+
fi
|
|
172
|
+
if ! has_chrome; then
|
|
173
|
+
# Playwright も失敗した場合、システム Chromium にフォールバック
|
|
174
|
+
if ! has_system_chrome; then
|
|
175
|
+
echo "casty: Chrome not found." >&2
|
|
176
|
+
if is_arm64_linux; then
|
|
177
|
+
echo "casty: Install with: sudo apt install chromium-browser" >&2
|
|
178
|
+
fi
|
|
179
|
+
exit 1
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
# 1日1回だけバックグラウンドで更新チェック
|
|
185
|
+
needs_update() {
|
|
186
|
+
[ ! -f "$STAMP" ] && return 0
|
|
187
|
+
local last=$(stat -c %Y "$STAMP" 2>/dev/null || stat -f %m "$STAMP" 2>/dev/null || echo 0)
|
|
188
|
+
local now=$(date +%s)
|
|
189
|
+
[ $(( now - last )) -gt 86400 ]
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if needs_update; then
|
|
193
|
+
if is_arm64_linux; then
|
|
194
|
+
(install_chromium_playwright) &>/dev/null &
|
|
195
|
+
else
|
|
196
|
+
(install_chromium && cleanup_old_browsers) &>/dev/null &
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
exec node "$CASTY_DIR/bin/casty.js" "$@"
|