tapback-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/commit-push.md +67 -0
- package/.claude/settings.local.json +43 -0
- package/.github/workflows/ci.yml +19 -0
- package/.github/workflows/release.yml +29 -0
- package/.prettierrc +6 -0
- package/CLAUDE.md +84 -0
- package/README.md +71 -0
- package/bin/cli.js +59 -0
- package/package.json +30 -0
- package/src/claudeStatus.js +113 -0
- package/src/config.js +26 -0
- package/src/html.js +594 -0
- package/src/proxy.js +99 -0
- package/src/server.js +214 -0
- package/src/tmux.js +31 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Commit and Push
|
|
2
|
+
|
|
3
|
+
新しいブランチを作成し、変更をコミットしてプッシュし、PRを作成します。
|
|
4
|
+
|
|
5
|
+
## ベースブランチ
|
|
6
|
+
|
|
7
|
+
- main
|
|
8
|
+
|
|
9
|
+
## 手順
|
|
10
|
+
|
|
11
|
+
1. **ブランチの作成**
|
|
12
|
+
- 現在のブランチ状況を確認
|
|
13
|
+
- 新しいブランチを作成するか、既存のブランチを使用するかをユーザーに確認
|
|
14
|
+
- ユーザーに新しいブランチ名を確認(提案する場合は feature/xxx, fix/xxx, docs/xxx などのプレフィックスを使用)
|
|
15
|
+
- ベースブランチから新しいブランチを作成(必要に応じてベースブランチを最新化)
|
|
16
|
+
|
|
17
|
+
2. **変更のコミット**
|
|
18
|
+
- git status で変更内容を確認
|
|
19
|
+
- git diff で差分を確認
|
|
20
|
+
- 明確な指示がない限り変更されているファイルはすべて含める。 `git add .` を使用
|
|
21
|
+
- 変更内容に応じた適切なコミットメッセージを作成
|
|
22
|
+
- 変更をステージングしてコミット
|
|
23
|
+
- コミットメッセージには以下を含める:
|
|
24
|
+
- 簡潔な変更内容の要約
|
|
25
|
+
- フッターに以下を追加:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
29
|
+
|
|
30
|
+
Co-Authored-By: Claude <noreply@anthropic.com>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
3. **リモートへのプッシュ**
|
|
34
|
+
- `git push -u origin {branch-name}` でリモートにプッシュ
|
|
35
|
+
- プッシュ後の状態を確認
|
|
36
|
+
|
|
37
|
+
4. **PRの作成**
|
|
38
|
+
- `gh pr create --assignee @me` でPRを作成し、操作しているユーザーを担当者にアサイン
|
|
39
|
+
- PRのタイトルはコミットメッセージの要約を使用
|
|
40
|
+
- PRの本文には以下を含める:
|
|
41
|
+
- `## Summary` - 変更内容の要約(1-3行)
|
|
42
|
+
- `## Test plan` - テスト方法のチェックリスト
|
|
43
|
+
- フッターに以下を追加:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- 作成後、PRのURLをユーザーに表示
|
|
50
|
+
|
|
51
|
+
5. **レビューコメントの追加**
|
|
52
|
+
- レビュアーに説明が必要な変更箇所がある場合、該当行にコメントを追加する
|
|
53
|
+
- 以下のコマンドで特定の行にコメントを追加:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
gh api repos/{owner}/{repo}/pulls/{PR番号}/comments \
|
|
57
|
+
-f body="コメント内容" \
|
|
58
|
+
-f path="ファイルパス" \
|
|
59
|
+
-F position=diff内の行位置 \
|
|
60
|
+
-f commit_id="$(git rev-parse HEAD)"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 注意事項
|
|
64
|
+
|
|
65
|
+
- ブランチ名は機能や修正内容がわかるように命名する
|
|
66
|
+
- コミット前に必ず変更内容を確認する
|
|
67
|
+
- .env や credentials.json などの機密情報をコミットしないよう注意する
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(swift build:*)",
|
|
5
|
+
"WebSearch",
|
|
6
|
+
"Bash(swiftformat:*)",
|
|
7
|
+
"Bash(git checkout:*)",
|
|
8
|
+
"Bash(git add:*)",
|
|
9
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nUI改善とメニューバー簡素化\n\n- メニューバーをOpen WindowとQuitのみに簡素化\n- PINステータスにCircleインジケーター追加\n- PIN表示の改行問題を修正\n- デフォルトproxyPortsを空に変更\n- セッション・設定の永続化\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
10
|
+
"Bash(grep:*)",
|
|
11
|
+
"WebFetch(domain:github.com)",
|
|
12
|
+
"WebFetch",
|
|
13
|
+
"Bash(swift package update:*)",
|
|
14
|
+
"Bash(git pull:*)",
|
|
15
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat: Add custom quick buttons for mobile interface\n\n- Add QuickButton model with label and command\n- Add quickButtons to ServerManager with persistence\n- Add Quick Button Settings UI in Mac app\n- Render custom buttons in mobile web interface with blue color scheme\n\nUsers can configure custom command buttons \\(e.g., /commit, /push\\) that\nappear on the mobile interface for quick access to frequently used commands.\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
16
|
+
"Bash(git push:*)",
|
|
17
|
+
"Bash(gh run view:*)",
|
|
18
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix: Update softprops/action-gh-release to v2\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
19
|
+
"Bash(tmux list-sessions:*)",
|
|
20
|
+
"Bash(tmux capture-pane:*)",
|
|
21
|
+
"Bash(claude --resume --help:*)",
|
|
22
|
+
"Bash(claude -r:*)",
|
|
23
|
+
"Bash(ls:*)",
|
|
24
|
+
"Bash(curl:*)",
|
|
25
|
+
"Bash(while read s)",
|
|
26
|
+
"Bash(done)",
|
|
27
|
+
"Bash(python3:*)",
|
|
28
|
+
"Bash(iconutil:*)",
|
|
29
|
+
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat: Add app icon\n\n- Add mobile+terminal design icon \\(smartphone with >_ prompt\\)\n- Configure Package.swift to include icon resource\n- Set app icon on launch in AppDelegate\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
|
|
30
|
+
"Bash(chmod:*)",
|
|
31
|
+
"Bash(npm install)",
|
|
32
|
+
"Bash(timeout 3 node:*)",
|
|
33
|
+
"Bash(lsof:*)",
|
|
34
|
+
"Bash(xargs kill:*)",
|
|
35
|
+
"Bash(git commit:*)",
|
|
36
|
+
"Bash(gh pr create:*)",
|
|
37
|
+
"Bash(gh pr checks:*)",
|
|
38
|
+
"Bash(npx prettier:*)",
|
|
39
|
+
"Bash(npm run format:check:*)",
|
|
40
|
+
"Bash(gh run list:*)"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: actions/setup-node@v4
|
|
15
|
+
with:
|
|
16
|
+
node-version: 20
|
|
17
|
+
cache: npm
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npm run format:check
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
13
|
+
id-token: write
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: 20
|
|
19
|
+
cache: npm
|
|
20
|
+
registry-url: https://registry.npmjs.org
|
|
21
|
+
- run: npm ci
|
|
22
|
+
- run: npm run format:check
|
|
23
|
+
- run: npm publish --provenance --access public
|
|
24
|
+
env:
|
|
25
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
26
|
+
- name: Create GitHub Release
|
|
27
|
+
env:
|
|
28
|
+
GH_TOKEN: ${{ github.token }}
|
|
29
|
+
run: gh release create ${{ github.ref_name }} --generate-notes
|
package/.prettierrc
ADDED
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
このファイルはClaude Codeがこのリポジトリで作業する際のガイダンスを提供します。
|
|
4
|
+
|
|
5
|
+
## プロジェクト概要
|
|
6
|
+
|
|
7
|
+
Tapbackは、モバイル端末からClaude Code/Codexのターミナルを監視・操作するNode.js CLIツールです。tmuxセッションの出力をWebSocket経由でリアルタイム配信し、localhostで動作するWebアプリへのリバースプロキシ機能も提供します。
|
|
8
|
+
|
|
9
|
+
## ビルドとテスト
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 依存インストール
|
|
13
|
+
npm install
|
|
14
|
+
|
|
15
|
+
# 実行
|
|
16
|
+
node bin/cli.js
|
|
17
|
+
|
|
18
|
+
# ポート指定
|
|
19
|
+
node bin/cli.js 8080
|
|
20
|
+
|
|
21
|
+
# プロキシ付き
|
|
22
|
+
node bin/cli.js --proxy 3000:3001
|
|
23
|
+
|
|
24
|
+
# PIN無効
|
|
25
|
+
node bin/cli.js --no-pin
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## アーキテクチャ
|
|
29
|
+
|
|
30
|
+
### ディレクトリ構造
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
tapback-node/
|
|
34
|
+
├── package.json # bin: tapback, dependencies
|
|
35
|
+
├── bin/cli.js # エントリーポイント (#!/usr/bin/env node)
|
|
36
|
+
├── src/
|
|
37
|
+
│ ├── server.js # Express + WebSocket サーバー (ポート9876)
|
|
38
|
+
│ ├── tmux.js # tmuxコマンド実行 (child_process.execFile)
|
|
39
|
+
│ ├── proxy.js # リバースプロキシ (localhost書き換え)
|
|
40
|
+
│ ├── claudeStatus.js # Claude Codeステータス管理 + hooks設置
|
|
41
|
+
│ ├── config.js # 設定ファイル (~/.tapback.json) 読み書き
|
|
42
|
+
│ └── html.js # モバイルUI HTML生成
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 主要コンポーネント
|
|
46
|
+
|
|
47
|
+
- **server.js**: Express + wsベースのサーバー。ターミナルUI、PIN認証(メイン/設定で別PIN)、WebSocket、設定API
|
|
48
|
+
- **tmux.js**: tmuxコマンド(capture-pane, send-keys, list-sessions, display-message)のPromiseラッパー
|
|
49
|
+
- **proxy.js**: http-proxyベースのリバースプロキシ。localhost参照を自動的にMacのIPに書き換え
|
|
50
|
+
- **claudeStatus.js**: Claude Codeフック設置、ステータス受信・保存。`~/.claude/settings.json`を更新
|
|
51
|
+
- **config.js**: `~/.tapback.json`による設定永続化(PIN有効/無効、プロキシポート、クイックボタン)
|
|
52
|
+
- **html.js**: モバイル向けレスポンシブWebUI、設定ページ、PIN認証ページ
|
|
53
|
+
|
|
54
|
+
### 通信フロー
|
|
55
|
+
|
|
56
|
+
1. モバイル → `/api/sessions` でtmuxセッション一覧取得
|
|
57
|
+
2. モバイル → `/ws` WebSocket接続
|
|
58
|
+
3. サーバー → 全tmuxセッションの出力を1秒ごとにブロードキャスト
|
|
59
|
+
4. モバイル → WebSocketでコマンド送信 → `tmux.sendKeys`でtmuxに転送
|
|
60
|
+
|
|
61
|
+
### 認証
|
|
62
|
+
|
|
63
|
+
- メインページ (`/`): 4桁PIN、Cookie(24h)。`--no-pin`または設定で無効化可能
|
|
64
|
+
- 設定ページ (`/settings`): 別の4桁PIN、常に有効
|
|
65
|
+
|
|
66
|
+
## コーディング規約
|
|
67
|
+
|
|
68
|
+
- Node.js (CommonJS)
|
|
69
|
+
- 非同期処理はasync/awaitとPromise
|
|
70
|
+
- サーバーはExpress + ws
|
|
71
|
+
|
|
72
|
+
## 依存関係
|
|
73
|
+
|
|
74
|
+
- **express**: HTTPサーバー
|
|
75
|
+
- **ws**: WebSocket
|
|
76
|
+
- **http-proxy**: リバースプロキシ
|
|
77
|
+
- **cookie-parser**: Cookie認証
|
|
78
|
+
|
|
79
|
+
## 注意点
|
|
80
|
+
|
|
81
|
+
- tmuxコマンドは明示的に`session:0.0`を指定(複数ウィンドウ/ペーン対応のため)
|
|
82
|
+
- PATH設定で`/opt/homebrew/bin`と`/usr/local/bin`を追加(tmux検出用)
|
|
83
|
+
- プロキシはlocalhost参照を自動的にMacのIPに書き換え
|
|
84
|
+
- 設定ファイルは`~/.tapback.json`に保存
|
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Tapback
|
|
2
|
+
|
|
3
|
+
モバイル端末からClaude Code/Codexのターミナルを監視・操作するNode.js CLIツール。
|
|
4
|
+
|
|
5
|
+
tmuxセッションの出力をWebSocket経由でリアルタイム配信し、スマホからターミナルの閲覧・コマンド入力ができます。
|
|
6
|
+
|
|
7
|
+
## 機能
|
|
8
|
+
|
|
9
|
+
- tmuxセッションのリアルタイム監視(1秒間隔)
|
|
10
|
+
- モバイルからのコマンド送信
|
|
11
|
+
- Claude Codeのステータス表示(hooks連携)
|
|
12
|
+
- localhostアプリのリバースプロキシ
|
|
13
|
+
- PIN認証(メインページ・設定ページで別PIN)
|
|
14
|
+
- Web設定画面(プロキシポート、クイックボタン)
|
|
15
|
+
|
|
16
|
+
## インストール
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 使い方
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# 基本起動(ポート9876)
|
|
26
|
+
node bin/cli.js
|
|
27
|
+
|
|
28
|
+
# ポート指定
|
|
29
|
+
node bin/cli.js 8080
|
|
30
|
+
|
|
31
|
+
# リバースプロキシ付き(localhost:3000 → :3001で外部公開)
|
|
32
|
+
node bin/cli.js --proxy 3000:3001
|
|
33
|
+
|
|
34
|
+
# PIN認証を無効化
|
|
35
|
+
node bin/cli.js --no-pin
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
起動すると以下が表示されます:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
Tapback is running!
|
|
42
|
+
URL: http://192.168.x.x:9876/
|
|
43
|
+
Settings: http://192.168.x.x:9876/settings
|
|
44
|
+
PIN: 1234
|
|
45
|
+
Settings PIN: 5678
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
スマホで表示されたURLにアクセスし、PINを入力してください。
|
|
49
|
+
|
|
50
|
+
## 設定
|
|
51
|
+
|
|
52
|
+
設定は`~/.tapback.json`に保存されます。Web UIの`/settings`からも編集可能です。
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"pinEnabled": true,
|
|
57
|
+
"proxyPorts": { "3000": 3001 },
|
|
58
|
+
"quickButtons": [{ "label": "push", "command": "/commit-push" }]
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Claude Code連携
|
|
63
|
+
|
|
64
|
+
起動時にClaude Codeのhooksを自動設置します(`~/.claude/hooks/tapback-status-hook.sh`)。
|
|
65
|
+
Claude Codeのセッション状態(starting/processing/idle/waiting/ended)がリアルタイムでモバイルUIに反映されます。
|
|
66
|
+
|
|
67
|
+
## 必要環境
|
|
68
|
+
|
|
69
|
+
- Node.js 18+
|
|
70
|
+
- tmux
|
|
71
|
+
- macOS(ローカルIP取得にen0を使用)
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { createServer, getLocalIP } = require('../src/server');
|
|
4
|
+
const { createProxyServer } = require('../src/proxy');
|
|
5
|
+
const { installHooks } = require('../src/claudeStatus');
|
|
6
|
+
const config = require('../src/config');
|
|
7
|
+
|
|
8
|
+
// Load saved config
|
|
9
|
+
const cfg = config.load();
|
|
10
|
+
|
|
11
|
+
// CLI args override config
|
|
12
|
+
let port = 9876;
|
|
13
|
+
const proxyPorts = { ...cfg.proxyPorts };
|
|
14
|
+
|
|
15
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
16
|
+
const arg = process.argv[i];
|
|
17
|
+
if (arg === '--proxy' && process.argv[i + 1]) {
|
|
18
|
+
const [target, external] = process.argv[++i].split(':').map(Number);
|
|
19
|
+
if (target && external) proxyPorts[String(target)] = external;
|
|
20
|
+
} else if (arg === '--no-pin') {
|
|
21
|
+
cfg.pinEnabled = false;
|
|
22
|
+
} else if (/^\d+$/.test(arg)) {
|
|
23
|
+
port = Number(arg);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Install Claude Code hooks
|
|
28
|
+
try {
|
|
29
|
+
installHooks(port);
|
|
30
|
+
console.log('[Tapback] Claude Code hooks installed');
|
|
31
|
+
} catch (e) {
|
|
32
|
+
console.warn('[Tapback] Failed to install hooks:', e.message);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const macIP = getLocalIP();
|
|
36
|
+
const firstExternalPort = Object.values(proxyPorts)[0];
|
|
37
|
+
|
|
38
|
+
const { pin, settingsPin } = createServer({
|
|
39
|
+
port,
|
|
40
|
+
pinEnabled: cfg.pinEnabled,
|
|
41
|
+
quickButtons: cfg.quickButtons,
|
|
42
|
+
appURL: firstExternalPort ? `http://${macIP}:${firstExternalPort}/` : null,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Start proxy servers
|
|
46
|
+
for (const [target, external] of Object.entries(proxyPorts)) {
|
|
47
|
+
createProxyServer(Number(target), Number(external), macIP, proxyPorts);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log(' Tapback is running!');
|
|
52
|
+
console.log(` URL: http://${macIP}:${port}/`);
|
|
53
|
+
console.log(` Settings: http://${macIP}:${port}/settings`);
|
|
54
|
+
console.log(` PIN: ${pin} ${cfg.pinEnabled ? '' : '(disabled)'}`);
|
|
55
|
+
console.log(` Settings PIN: ${settingsPin}`);
|
|
56
|
+
console.log('');
|
|
57
|
+
|
|
58
|
+
process.on('SIGINT', () => process.exit(0));
|
|
59
|
+
process.on('SIGTERM', () => process.exit(0));
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tapback-cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Monitor and control tmux sessions from your mobile device",
|
|
5
|
+
"bin": {
|
|
6
|
+
"tapback": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "node bin/cli.js",
|
|
10
|
+
"format": "prettier --write .",
|
|
11
|
+
"format:check": "prettier --check ."
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"tmux",
|
|
15
|
+
"terminal",
|
|
16
|
+
"mobile",
|
|
17
|
+
"claude-code"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"chalk": "^5.3.0",
|
|
22
|
+
"cookie-parser": "^1.4.6",
|
|
23
|
+
"express": "^4.18.2",
|
|
24
|
+
"http-proxy": "^1.18.1",
|
|
25
|
+
"ws": "^8.16.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"prettier": "^3.8.1"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
class ClaudeStatusStore {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.statuses = new Map(); // key: project_dir or session_id
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
update(status) {
|
|
11
|
+
const key = status.project_dir || status.session_id;
|
|
12
|
+
this.statuses.set(key, { ...status, timestamp: new Date().toISOString() });
|
|
13
|
+
// Clean up old (>1h)
|
|
14
|
+
const cutoff = Date.now() - 3600000;
|
|
15
|
+
for (const [k, v] of this.statuses) {
|
|
16
|
+
if (new Date(v.timestamp).getTime() < cutoff) this.statuses.delete(k);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getAll() {
|
|
21
|
+
return [...this.statuses.values()].sort(
|
|
22
|
+
(a, b) => new Date(b.timestamp) - new Date(a.timestamp),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function installHooks(port = 9876) {
|
|
28
|
+
const homeDir = os.homedir();
|
|
29
|
+
const hooksDir = path.join(homeDir, '.claude', 'hooks');
|
|
30
|
+
const hookScriptPath = path.join(hooksDir, 'tapback-status-hook.sh');
|
|
31
|
+
const settingsPath = path.join(homeDir, '.claude', 'settings.json');
|
|
32
|
+
|
|
33
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
34
|
+
|
|
35
|
+
const hookScript = `#!/bin/bash
|
|
36
|
+
# Tapback Status Hook for Claude Code
|
|
37
|
+
# Auto-installed by Tapback
|
|
38
|
+
|
|
39
|
+
set -e
|
|
40
|
+
|
|
41
|
+
input=$(cat)
|
|
42
|
+
|
|
43
|
+
hook_event_name=$(echo "$input" | jq -r '.hook_event_name // empty')
|
|
44
|
+
session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
45
|
+
cwd=$(echo "$input" | jq -r '.cwd // empty')
|
|
46
|
+
model=$(echo "$input" | jq -r '.model // empty')
|
|
47
|
+
|
|
48
|
+
if [ -z "$session_id" ]; then
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
case "$hook_event_name" in
|
|
53
|
+
"SessionStart") status="starting" ;;
|
|
54
|
+
"UserPromptSubmit") status="processing" ;;
|
|
55
|
+
"Stop") status="idle" ;;
|
|
56
|
+
"Notification")
|
|
57
|
+
notification_type=$(echo "$input" | jq -r '.notification_type // empty')
|
|
58
|
+
if [ "$notification_type" = "idle_prompt" ]; then
|
|
59
|
+
status="waiting"
|
|
60
|
+
else
|
|
61
|
+
exit 0
|
|
62
|
+
fi
|
|
63
|
+
;;
|
|
64
|
+
"SessionEnd") status="ended" ;;
|
|
65
|
+
*) exit 0 ;;
|
|
66
|
+
esac
|
|
67
|
+
|
|
68
|
+
TAPBACK_URL="\${TAPBACK_URL:-http://localhost:${port}}"
|
|
69
|
+
|
|
70
|
+
curl -s -X POST "\${TAPBACK_URL}/api/claude-status" \\
|
|
71
|
+
-H "Content-Type: application/json" \\
|
|
72
|
+
-d "{\\"session_id\\":\\"$session_id\\",\\"status\\":\\"$status\\",\\"project_dir\\":\\"$cwd\\",\\"model\\":\\"$model\\"}" \\
|
|
73
|
+
>/dev/null 2>&1 || true
|
|
74
|
+
|
|
75
|
+
exit 0
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
fs.writeFileSync(hookScriptPath, hookScript, { mode: 0o755 });
|
|
79
|
+
|
|
80
|
+
// Update settings.json
|
|
81
|
+
const hooksConfig = {};
|
|
82
|
+
const hookCommand = hookScriptPath;
|
|
83
|
+
for (const event of ['SessionStart', 'UserPromptSubmit', 'Stop', 'SessionEnd']) {
|
|
84
|
+
hooksConfig[event] = [{ hooks: [{ type: 'command', command: hookCommand }] }];
|
|
85
|
+
}
|
|
86
|
+
hooksConfig['Notification'] = [
|
|
87
|
+
{ matcher: 'idle_prompt', hooks: [{ type: 'command', command: hookCommand }] },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
let settings = {};
|
|
91
|
+
try {
|
|
92
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
93
|
+
} catch {}
|
|
94
|
+
|
|
95
|
+
const existing = settings.hooks || {};
|
|
96
|
+
for (const [key, value] of Object.entries(hooksConfig)) {
|
|
97
|
+
const arr = existing[key] || [];
|
|
98
|
+
const hasTapback = arr.some(
|
|
99
|
+
(item) =>
|
|
100
|
+
item.hooks &&
|
|
101
|
+
item.hooks.some((h) => h.command && h.command.includes('tapback-status-hook.sh')),
|
|
102
|
+
);
|
|
103
|
+
if (!hasTapback) {
|
|
104
|
+
existing[key] = [...arr, ...value];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
settings.hooks = existing;
|
|
108
|
+
|
|
109
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { ClaudeStatusStore, installHooks };
|
package/src/config.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const CONFIG_PATH = path.join(os.homedir(), '.tapback.json');
|
|
6
|
+
|
|
7
|
+
const DEFAULTS = {
|
|
8
|
+
pinEnabled: true,
|
|
9
|
+
proxyPorts: {}, // { "3000": 3001 } = localhost:3000 -> :3001
|
|
10
|
+
quickButtons: [], // [{ label, command }]
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function load() {
|
|
14
|
+
try {
|
|
15
|
+
const data = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
16
|
+
return { ...DEFAULTS, ...data };
|
|
17
|
+
} catch {
|
|
18
|
+
return { ...DEFAULTS };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function save(config) {
|
|
23
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { load, save, CONFIG_PATH };
|