@interactive-inc/claude-funnel 0.36.0 → 0.38.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/README.md +244 -322
- package/dist/bin.js +554 -509
- package/dist/gateway/daemon.js +275 -237
- package/dist/index.d.ts +1904 -2308
- package/dist/index.js +1698 -312
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,97 +1,53 @@
|
|
|
1
1
|
[](https://www.npmjs.com/package/@interactive-inc/claude-funnel)
|
|
2
2
|
[](./LICENSE)
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
# Open Claude Funnel
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Connectors today: Slack (Socket Mode), GitHub (poll via `gh`), Discord (Gateway), and cron schedules. Built around Claude Code; the architecture is agent-agnostic.
|
|
9
|
-
|
|
10
|
-
## Why funnel
|
|
11
|
-
|
|
12
|
-
A single agent session is great at one repository at one moment. The moment you want it to react to things — a chat mention, a new issue, a 9 AM standup — you end up gluing shell scripts, cron entries, and `bash -c "agent ..."` invocations together. There is no single place that says "who is listening to what, and who is allowed to reply where."
|
|
13
|
-
|
|
14
|
-
funnel is that place. Declare named subscription boxes (channels), attach connectors to them, launch the agent with a channel binding, and the daemon handles the rest:
|
|
15
|
-
|
|
16
|
-
- The daemon owns the external connections. Each one connects once, no matter how many agent sessions you start. A second agent does not open a second socket; both sessions subscribe to the same channel and the daemon fans events out.
|
|
17
|
-
- Inbound events arrive as MCP notifications, so the agent reacts in the session it is already running in.
|
|
18
|
-
- Outbound replies use MCP tools per connector — essentially synchronous (no bash, no CLI cold start).
|
|
19
|
-
- Listeners are supervised with health checks and automatic restart; a flaky connection or crashed poller recovers on its own.
|
|
20
|
-
- Multiple agents can share a channel (`fanout`) or compete for events as workers (`exclusive`) — the daemon decides who gets each event.
|
|
21
|
-
|
|
22
|
-
## Concepts
|
|
6
|
+
Slack のメンション、GitHub の Issue、毎朝 9 時の cron。こうした外部の出来事を Claude Code エージェントに届け、エージェントの返信を同じ経路で外へ返すハブ。
|
|
23
7
|
|
|
24
8
|
```
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
▼ WebSocket / MCP (stdio)
|
|
34
|
-
agent (subscribes to one channel)
|
|
9
|
+
Slack ─┐ ┌─→ Claude エージェント A
|
|
10
|
+
GitHub ─┤ │
|
|
11
|
+
Discord ─┼──→ funnel daemon ──→┼─→ Claude エージェント B
|
|
12
|
+
cron ─┘ │
|
|
13
|
+
└─→ Claude エージェント C
|
|
14
|
+
|
|
15
|
+
funnel daemon が外部接続を1か所に常駐保持し、購読中の各エージェントへ配信する。
|
|
16
|
+
返信は同じコネクタを逆向きに通る(エージェント → MCP tool → 外部サービス)。
|
|
35
17
|
```
|
|
36
18
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Channel — a named subscription box (transport only). Holds one or more connectors and a delivery mode; it does not carry launch flags. An agent session subscribes to exactly one channel. Delivery is `fanout` (every subscriber sees every event, the default) or `exclusive` (one event per subscriber, round-robin — for worker pools).
|
|
40
|
-
|
|
41
|
-
Connector — a single attachment from a channel to an external source. Four types ship today: `slack`, `gh`, `discord`, `schedule`. The first three are bidirectional (events in, replies out); `schedule` is one-way (cron ticks in).
|
|
19
|
+
コマンドは `funnel`(短縮形 `fnl`)。Claude Code を中心に作っているが、アーキテクチャはエージェント非依存。
|
|
42
20
|
|
|
43
|
-
|
|
21
|
+
## funnel がやること
|
|
44
22
|
|
|
45
|
-
|
|
23
|
+
- 外部の出来事(チャットのメンション、新しい Issue、cron の tick)を、起動中のエージェントセッションにそのまま届ける
|
|
24
|
+
- エージェントの返信を受信と同じコネクタで外へ返す。bash サブシェルも CLI のコールドスタートもなく、実質同期
|
|
25
|
+
- 外部接続は常駐デーモンが 1 か所で持つ。エージェントを何個起こしても接続は 1 回だけ。ヘルスチェックと自動再起動で監視する
|
|
26
|
+
- 複数のエージェントで 1 つのソースを共有(fanout)するか、ワーカーとして分担(exclusive)するかを選べる
|
|
46
27
|
|
|
47
|
-
|
|
28
|
+
対応コネクタは Slack(Socket Mode)、GitHub(`gh` 経由の poll)、Discord(Gateway)、cron スケジュールの 4 種類。
|
|
48
29
|
|
|
49
|
-
##
|
|
30
|
+
## 必要なもの
|
|
50
31
|
|
|
51
|
-
- [Bun](https://bun.sh) 1.3
|
|
32
|
+
- [Bun](https://bun.sh) 1.3 以降
|
|
52
33
|
- [Claude Code](https://docs.claude.com/en/docs/claude-code) CLI
|
|
53
|
-
-
|
|
34
|
+
- 接続する外部サービスのトークンまたは CLI(Slack アプリ、`gh` 認証、Discord bot など)
|
|
54
35
|
|
|
55
|
-
##
|
|
36
|
+
## リポジトリで使う(推奨)
|
|
56
37
|
|
|
57
|
-
|
|
38
|
+
設定を `funnel.json` としてリポジトリに commit し、チームで共有・バージョン管理できる。グローバルに何も入れないので、リポジトリの lock ファイルでバージョンが固定される。
|
|
58
39
|
|
|
59
|
-
|
|
60
|
-
bun add -D @interactive-inc/claude-funnel
|
|
61
|
-
bunx funnel claude # or `bunx funnel <any subcommand>`
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Global (one CLI for every repo you touch):
|
|
40
|
+
### インストール
|
|
65
41
|
|
|
66
42
|
```bash
|
|
67
|
-
bun add -
|
|
68
|
-
funnel claude # or the `fnl` shorthand
|
|
43
|
+
bun add -D @interactive-inc/claude-funnel
|
|
69
44
|
```
|
|
70
45
|
|
|
71
|
-
|
|
46
|
+
これで `bunx funnel`(または `bunx fnl`)が使える。
|
|
72
47
|
|
|
73
|
-
|
|
48
|
+
### 設定
|
|
74
49
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
fnl channels add ops
|
|
79
|
-
fnl channels ops connectors add my-slack --type=slack \
|
|
80
|
-
--bot-token=xoxb-... --app-token=xapp-...
|
|
81
|
-
fnl gateway start
|
|
82
|
-
fnl claude --channel ops
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Every event the connector sees now arrives in the running agent session, and the agent can reply via the `my-slack` MCP tool.
|
|
86
|
-
|
|
87
|
-
Save it as a profile for one-command launches — the profile carries the launch recipe:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
fnl profiles add cto --path=/repo/myapp --channel=ops --agent=pm --options="--brief"
|
|
91
|
-
fnl claude --profile cto # cd + channel binding + recipe in one shot
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo will use it:
|
|
50
|
+
リポジトリ直下に `funnel.json` を置く。transport(`channels[]`)と起動レシピ(`profiles[]`)を宣言する。
|
|
95
51
|
|
|
96
52
|
```json
|
|
97
53
|
{
|
|
@@ -99,12 +55,7 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
|
|
|
99
55
|
"channels": [
|
|
100
56
|
{
|
|
101
57
|
"name": "ops",
|
|
102
|
-
"connectors": [
|
|
103
|
-
{
|
|
104
|
-
"type": "slack",
|
|
105
|
-
"name": "my-slack"
|
|
106
|
-
}
|
|
107
|
-
]
|
|
58
|
+
"connectors": [{ "type": "slack", "name": "my-slack" }]
|
|
108
59
|
},
|
|
109
60
|
{
|
|
110
61
|
"name": "review"
|
|
@@ -112,11 +63,14 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
|
|
|
112
63
|
],
|
|
113
64
|
"profiles": [
|
|
114
65
|
{
|
|
66
|
+
"name": "pm",
|
|
115
67
|
"channel": "ops",
|
|
116
68
|
"options": ["--brief", "--agent", "pm"],
|
|
117
|
-
"env": { "ANTHROPIC_MODEL": "claude-sonnet-4-6" }
|
|
69
|
+
"env": { "ANTHROPIC_MODEL": "claude-sonnet-4-6" },
|
|
70
|
+
"resume": true
|
|
118
71
|
},
|
|
119
72
|
{
|
|
73
|
+
"name": "reviewer",
|
|
120
74
|
"channel": "review",
|
|
121
75
|
"options": ["--agent", "reviewer"]
|
|
122
76
|
}
|
|
@@ -124,114 +78,165 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
|
|
|
124
78
|
}
|
|
125
79
|
```
|
|
126
80
|
|
|
127
|
-
|
|
81
|
+
channels は購読箱(transport)。各チャネルは `connectors` を持てる。`connectors` 配列はそのチャネルの真実の源で、起動時に宣言にないコネクタは削除され、足りないコネクタは作られる(名前で照合)。`connectors` フィールド自体が無ければ既存のコネクタはそのまま残る。
|
|
82
|
+
|
|
83
|
+
profiles は起動レシピ。各プロファイルは一意な `name` を持ち、`channel` でチャネルを名前指定でバインドし、次を持つ。
|
|
128
84
|
|
|
129
|
-
|
|
85
|
+
- `options` — claude の argv 先頭に積む(ユーザーが渡す CLI 引数はその後ろ。`--brief` / `--agent <name>` / `--model <name>` などに使う)
|
|
86
|
+
- `env` — 起動する claude プロセスに被せる(衝突時は起動シェルの `process.env` が勝つ)
|
|
87
|
+
- `resume` — claude セッションの再利用可否
|
|
130
88
|
|
|
131
|
-
|
|
89
|
+
同じチャネルに複数のプロファイルをバインドしてよく、`name` で区別する。チャネル側がプロファイルを選ぶことはない(プロファイルがチャネルを bind する一方向)。
|
|
132
90
|
|
|
133
|
-
|
|
91
|
+
トークンは `funnel.json` に書かない(スキーマが弾く)。リポジトリ内で次のどちらか。
|
|
134
92
|
|
|
135
|
-
|
|
93
|
+
- `fnl channels ops connectors set my-slack --bot-token=xoxb-... --app-token=xapp-...` で設定する
|
|
94
|
+
- 省略して `fnl claude` 起動時の TTY プロンプトで答える。次回以降は引き継がれ再度聞かれない
|
|
136
95
|
|
|
137
|
-
|
|
96
|
+
いずれもトークンは `~/.funnel/projects/<id>/settings.json`(リポジトリ外)に保存され、commit されない。
|
|
138
97
|
|
|
139
|
-
|
|
140
|
-
- or omit it — `fnl claude` prompts on a TTY at launch and saves the answer there; it carries over so later launches do not re-prompt. On non-TTY stdin the launch fails so CI / agent-spawned-agent runs do not hang
|
|
98
|
+
`funnel.json` に書けないものが 2 つある。delivery モード(fanout / exclusive)は CLI で設定する(`funnel.json` のチャネルは fanout 固定)。schedule の cron エントリも CLI で足す(`funnel.json` には schedule コネクタの存在だけ宣言できる)。
|
|
141
99
|
|
|
142
|
-
|
|
100
|
+
セッション再開には `resume` の明示が要る。`funnel.json` の `resume` はデフォルト値を持たず、書かないと再開されない(未指定が false 扱いになる)。前回の claude セッションを引き継ぎたいなら必ず `"resume": true` を書く。再開のための session id は funnel が `~/.funnel/projects/<id>/settings.json` に自動で保存・読み出しするので、`funnel.json` には書かない。
|
|
143
101
|
|
|
144
|
-
|
|
102
|
+
### 使い方
|
|
145
103
|
|
|
146
104
|
```bash
|
|
147
|
-
fnl
|
|
148
|
-
fnl
|
|
105
|
+
bunx fnl gateway start # 常駐デーモンを起動(外部接続を持つ)
|
|
106
|
+
bunx fnl claude --profile pm # cd + チャネルのバインド + レシピを一発で
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`--profile` なしの `bunx fnl claude` は、`funnel.json` の先頭チャネルで起動する。`--channel review` で名前指定すると、そのチャネルを transport だけバインドして起動する(レシピなし)。
|
|
110
|
+
|
|
111
|
+
これでコネクタが見たイベントが、起動中のエージェントセッションに届く。エージェントは `my-slack` という MCP ツールで返信できる。
|
|
112
|
+
|
|
113
|
+
cron 起動を足す(schedule コネクタを宣言したうえで、エントリは CLI で)。
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
bunx fnl channels ops connectors add daily --type=schedule
|
|
117
|
+
bunx fnl channels ops connectors daily schedules add morning \
|
|
149
118
|
--cron="0 9 * * *" --prompt="morning standup"
|
|
150
119
|
```
|
|
151
120
|
|
|
152
|
-
|
|
121
|
+
tick ごとにプロンプトがチャネルへ発火する。9 時にデーモンが落ちていても、次回起動時に逃した枠を catch-up する(`meta.catchup = "true"`、最大 24 時間)。
|
|
122
|
+
|
|
123
|
+
`funnel.json` を持つリポジトリは自分自身にスコープされる。初回起動時に funnel は `funnel.json` の先頭へ不変の `id`(uuid) を書き戻し、以降このリポジトリの funnel state を `~/.funnel/projects/<id>/` 配下に隔離する。グローバルの `~/.funnel` には一切触らない(イベントログと一時ファイルだけが `/tmp/funnel/` で共有)。このスコープはリポジトリ内で実行する全 CLI コマンドに効く。
|
|
124
|
+
|
|
125
|
+
トップレベルの `$schema`(任意)は JSON Schema を指し、エディタの検証と補完が効く。ローカルインストールでは `./node_modules/@interactive-inc/claude-funnel/funnel.schema.json` を推奨する(ネットワーク往復なし)。`https://interactive-inc.github.io/open-claude-funnel/funnel.schema.json` でも公開しており、`fnl schema > funnel.schema.json` でローカルコピーを再生成できる。
|
|
153
126
|
|
|
154
|
-
|
|
127
|
+
## グローバルで使う
|
|
128
|
+
|
|
129
|
+
触るすべてのリポジトリで 1 つの CLI を共有したいときはグローバルに入れる。設定は `funnel.json` ではなく CLI で組み、`~/.funnel/settings.json`(グローバル)に保存される。
|
|
155
130
|
|
|
156
131
|
```bash
|
|
157
|
-
|
|
158
|
-
|
|
132
|
+
bun add -g @interactive-inc/claude-funnel
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
これで `funnel` / `fnl` がどこでも使える。ソース 1 つをエージェント 1 つにつなぐ最短手順。
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
fnl channels add ops
|
|
139
|
+
fnl channels ops connectors add my-slack --type=slack \
|
|
140
|
+
--bot-token=xoxb-... --app-token=xapp-...
|
|
141
|
+
fnl gateway start
|
|
142
|
+
fnl claude --channel ops
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
delivery モードはチャネル作成時に選ぶ。
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
fnl channels add reviews # fanout(デフォルト): 全エージェントが全イベント
|
|
149
|
+
fnl channels add ingest --delivery=exclusive # exclusive: 1 イベントを 1 エージェントが round-robin
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
ワンコマンド起動のためにプロファイルとして保存する。プロファイルは起動レシピを持つ。
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
fnl profiles add cto --path=/repo/myapp --channel=ops --agent=pm --options="--brief"
|
|
156
|
+
fnl claude --profile cto
|
|
159
157
|
```
|
|
160
158
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
fnl profiles remove <name>
|
|
199
|
-
|
|
200
|
-
fnl claude launch the first channel from ./funnel.json, or the default profile
|
|
201
|
-
fnl claude --channel <name> with funnel.json: pick that channel; without: raw launch
|
|
202
|
-
fnl claude --profile <name> launch a named profile (ignores funnel.json; cannot combine with --channel)
|
|
203
|
-
fnl claude [...] positionals and any flag other than -p / --profile / --channel
|
|
204
|
-
(e.g. --agent, --resume, -c, --model) pass through to claude
|
|
205
|
-
fnl mcp run as an MCP server (invoked from .mcp.json)
|
|
206
|
-
|
|
207
|
-
fnl gateway status (default subcommand)
|
|
208
|
-
fnl gateway {start|stop|restart} daemon lifecycle
|
|
209
|
-
fnl gateway run foreground daemon (developer mode)
|
|
210
|
-
fnl gateway logs [-n <N>] tail diagnostic log
|
|
211
|
-
fnl gateway listeners live registry (alive / dead)
|
|
212
|
-
|
|
213
|
-
fnl status overall status (channels / profiles / gateway / clients)
|
|
214
|
-
fnl schema print the JSON Schema for funnel.json (pipe to a file for editor support)
|
|
215
|
-
fnl update `bun i -g @interactive-inc/claude-funnel`
|
|
216
|
-
fnl (no args) show help
|
|
217
|
-
|
|
218
|
-
fnl --version
|
|
219
|
-
fnl --help every subcommand has --help; verb-without-arg also returns help
|
|
159
|
+
公開パッケージはビルド済みの `dist/` を同梱しているので、どちらのインストールでも `funnel` / `fnl` がすぐ使える(post-install ステップなし)。リポジトリ単位で入れたなら、以降の `fnl` は `bunx funnel` に読み替える。
|
|
160
|
+
|
|
161
|
+
コマンドの一覧やフラグはここに並べない。`fnl --help` で全体、`fnl <command> --help`(例 `fnl channels --help`)で個別の使い方が出る。動詞だけを引数なしで打ってもヘルプが返る。
|
|
162
|
+
|
|
163
|
+
## なぜ funnel なのか
|
|
164
|
+
|
|
165
|
+
1 つのエージェントセッションは、1 つのリポジトリ・1 つの瞬間を扱うのは得意だ。だがそれに「何かに反応させたい」と思った瞬間 — チャットのメンション、新しい Issue、朝 9 時のスタンドアップ — シェルスクリプトと cron と `bash -c "agent ..."` をつなぎ合わせる羽目になる。「誰が何を聞いていて、誰がどこに返信していいのか」を一望できる場所がどこにもない。
|
|
166
|
+
|
|
167
|
+
funnel はその場所になる。名前付きの購読箱(チャネル)を宣言し、コネクタを取り付け、チャネルをバインドしてエージェントを起動すれば、あとはデーモンが面倒を見る。使うモチベーションは次の点にある。
|
|
168
|
+
|
|
169
|
+
外部接続をデーモンが 1 か所で持つ。各接続は、エージェントセッションを何個起こしても 1 回だけつながる。2 つ目のエージェントが 2 つ目のソケットを開くことはなく、両方が同じチャネルを購読し、デーモンがイベントを fanout する。
|
|
170
|
+
|
|
171
|
+
受信イベントは MCP 通知として届くので、エージェントは今いるセッションのまま反応する。新しいプロセスを起こさない。
|
|
172
|
+
|
|
173
|
+
送信(返信)はコネクタごとの MCP ツールを使う。bash も CLI のコールドスタートもなく、実質同期で返る。
|
|
174
|
+
|
|
175
|
+
リスナーはヘルスチェックと自動再起動で監視される。接続が不安定でも poller がクラッシュしても、自分で復旧する。
|
|
176
|
+
|
|
177
|
+
複数のエージェントが 1 つのチャネルを共有する(fanout)か、ワーカーとしてイベントを取り合う(exclusive)かを選べる。どのイベントを誰が受けるかはデーモンが決める。
|
|
178
|
+
|
|
179
|
+
つまり、外部とのつなぎ込みという「配管」を 1 つのデーモンに集約し、エージェント側は購読と返信という単純な世界だけを見ればよくなる。これが funnel を使う理由になる。
|
|
180
|
+
|
|
181
|
+
## 仕組み
|
|
182
|
+
|
|
183
|
+
### 全体像
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
external sources outbound replies
|
|
187
|
+
(chat / source-control / cron) (MCP tools per connector)
|
|
188
|
+
│ │
|
|
189
|
+
▼ ▼
|
|
190
|
+
daemon (port 9743 for CLI)
|
|
191
|
+
routes events into channels
|
|
192
|
+
serves replies through the same connectors
|
|
193
|
+
│
|
|
194
|
+
▼ WebSocket / MCP (stdio)
|
|
195
|
+
agent (subscribes to one channel)
|
|
220
196
|
```
|
|
221
197
|
|
|
222
|
-
|
|
198
|
+
### Channel と Connector と Profile
|
|
199
|
+
|
|
200
|
+
transport モデルは 2 つの概念でできている。
|
|
201
|
+
|
|
202
|
+
Channel は名前付きの購読箱(transport のみ)。1 つ以上のコネクタと delivery モードを持ち、起動フラグは持たない。エージェントセッションはちょうど 1 つのチャネルを購読する。delivery は `fanout`(全 subscriber が全イベントを見る、デフォルト)か `exclusive`(1 イベントを 1 subscriber が round-robin で消費、ワーカープール向け)。
|
|
203
|
+
|
|
204
|
+
Connector はチャネルから外部ソースへの 1 つの接続。`slack` / `gh` / `discord` / `schedule` の 4 型。前者 3 つは双方向(イベント入力・返信出力)、`schedule` は一方向(cron tick の入力のみ)。
|
|
223
205
|
|
|
224
|
-
|
|
206
|
+
Profile はその transport モデルの外側にある起動の便宜レイヤで、モデルの一部ではない。エージェントを動かすのに必須ではない(`fnl claude --channel <name>` で足りる)。`{ path, channelId, options, env, resume }` を束ねた保存済みの起動 preset で、`fnl claude --profile cto` が既知のセットアップを再現する。どのディレクトリから起動するか、どのチャネルをバインドするか、起動レシピ(claude argv の先頭に積む引数、プロセスに被せる env、セッション再利用)をまとめて持つ。プロファイルは不変の uuid `id` を持ち(PID ファイルと再開可能なセッションがこれをキーにするので、rename してもどちらも迷子にならない)、`name` は人が打つハンドル。プロファイルは既にチャネルをバインドしているので、`--profile` と `--channel` は併用できない。
|
|
225
207
|
|
|
226
|
-
|
|
208
|
+
### daemon
|
|
209
|
+
|
|
210
|
+
外部接続はすべてデーモンに住む。`funnel` CLI 起動では port 9743 で動く(プログラムから起こす gateway は 9742 なので、1 マシン上で衝突しない。`FUNNEL_PORT` でどちらも上書き可)。bind は loopback(`127.0.0.1`)のみで off-box から到達不可。`FUNNEL_HOST=0.0.0.0` で意図的に公開できる(公開しても全特権エンドポイントは bearer token 必須)。デーモンはコネクタを自動再起動付きで監視し、イベントを WebSocket で購読中のエージェントセッションへ broadcast し、MCP が呼ぶ返信 API を提供する。エージェントの起動・停止が外部接続を起動・停止することはない。
|
|
211
|
+
|
|
212
|
+
### MCP
|
|
213
|
+
|
|
214
|
+
MCP レイヤはエージェントへの薄いブリッジ。バインドされたチャネルを WebSocket で購読し(実作業はデーモンがやる)、呼び出し可能なコネクタごとに 1 つのツールを公開して、エージェントが外へ返信できるようにする。
|
|
215
|
+
|
|
216
|
+
### イベントの旅
|
|
217
|
+
|
|
218
|
+
1 つの Slack メッセージがエージェントに届くまで。
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
Slack → SlackListener.start(notify) → notify(channel, connector, content, meta)
|
|
222
|
+
→ GatewayServer.notify → Broadcaster.broadcast → event store に seq 付き保存
|
|
223
|
+
→ 該当 Channel を購読している WS クライアントに fanout
|
|
224
|
+
→ エージェント側 MCP(channel-server)が受信してエージェントに events として渡す
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
逆方向(エージェント → Slack)は MCP のコネクタごとの tool 経由。Listener と Adapter は独立した一方向の通路で、Broadcaster は経由しない。
|
|
228
|
+
|
|
229
|
+
### 外部への送信
|
|
230
|
+
|
|
231
|
+
`fnl claude` がエージェントを起動すると、funnel の MCP サーバがデーモンに接続し、チャネルのコネクタを `~/.funnel/settings.json` から読む。呼び出し可能なコネクタ(`slack` / `discord` / `gh`。`schedule` は一方向なので除外)ごとに、コネクタ名のツールを 1 つ公開する。エージェントはこう呼ぶ。
|
|
227
232
|
|
|
228
233
|
```jsonc
|
|
229
|
-
// MCP: tools/list
|
|
234
|
+
// MCP: tools/list が返す
|
|
230
235
|
{ "name": "discord", "inputSchema": { ... { method, path, body } ... } }
|
|
231
236
|
{ "name": "ops-slack", "inputSchema": { ... } }
|
|
232
237
|
{ "name": "gh-main", "inputSchema": { ... } }
|
|
233
238
|
|
|
234
|
-
//
|
|
239
|
+
// エージェントの呼び出し
|
|
235
240
|
tools/call name="discord" arguments={
|
|
236
241
|
"method": "POST",
|
|
237
242
|
"path": "/channels/123/messages",
|
|
@@ -239,243 +244,160 @@ tools/call name="discord" arguments={
|
|
|
239
244
|
}
|
|
240
245
|
```
|
|
241
246
|
|
|
242
|
-
MCP
|
|
247
|
+
MCP は HTTP `POST /channels/<channel>/connectors/<connector>/call` でデーモンへ転送し、デーモンがコネクタの adapter 経由でディスパッチする。bash サブシェルもコールドスタートもなく、返信は実質同期。
|
|
243
248
|
|
|
244
|
-
|
|
249
|
+
エージェントの外からコネクタを呼ぶには、同じパスを `fnl channels <ch> connectors <c> request --method=<...> [--key=value ...]` で叩ける。
|
|
245
250
|
|
|
246
|
-
|
|
251
|
+
### データモデル
|
|
247
252
|
|
|
248
253
|
```
|
|
249
254
|
Channel = { id, name, delivery, connectors[] }
|
|
250
|
-
|
|
251
|
-
|
|
255
|
+
購読箱(transport のみ)。delivery は `fanout`(全 WS クライアントが全イベント受信)
|
|
256
|
+
か `exclusive`(round-robin で 1 イベント 1 クライアント)。起動設定は持たない。
|
|
252
257
|
|
|
253
258
|
Connector =
|
|
254
259
|
| { type: "slack", name, botToken, appToken } Slack Socket Mode
|
|
255
|
-
| { type: "gh", name, pollInterval? } GitHub
|
|
260
|
+
| { type: "gh", name, pollInterval? } GitHub(gh CLI, poll ベース)
|
|
256
261
|
| { type: "discord", name, botToken } Discord Gateway
|
|
257
|
-
| { type: "schedule", name, entries[] } cron
|
|
262
|
+
| { type: "schedule", name, entries[] } cron 起動。entries = { id, cron, prompt, enabled?, catchupPolicy? }
|
|
258
263
|
|
|
259
264
|
Profile = { id, name, path, channelId, options[], env, resume, sessionId? }
|
|
260
|
-
|
|
261
|
-
options[]
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
名前付き起動 preset: どこで起動するか(path)、どのチャネルをバインドするか、起動レシピ。
|
|
266
|
+
options[] は claude argv の先頭に積み、env はプロセスに被せ(衝突時は process.env が勝つ)、
|
|
267
|
+
resume はセッション再利用を切り替える。先頭がデフォルト。id は不変の uuid(PID ファイルと
|
|
268
|
+
再開可能セッションがぶら下がるキー。rename でどちらも迷子にしない)、name は CLI のハンドル。
|
|
269
|
+
sessionId は config ではなく実行状態で、このプロファイルが最後に起動した claude セッション。
|
|
270
|
+
launcher が書き、次回 resume で読み戻す。
|
|
266
271
|
|
|
267
272
|
LocalConfig = { id?, channels: ChannelSpec[], profiles?: ProfileSpec[] }
|
|
268
|
-
|
|
269
|
-
id
|
|
270
|
-
~/.funnel/projects/<id>/
|
|
273
|
+
リポジトリ単位のファイル(funnel.json)。channels[] 必須、先頭がデフォルト、--channel で選ぶ。
|
|
274
|
+
id(uuid)は初回起動時に書き戻され、このリポジトリの funnel state は
|
|
275
|
+
~/.funnel/projects/<id>/ 配下に住み、グローバルの ~/.funnel には一切触れない。
|
|
271
276
|
|
|
272
277
|
ChannelSpec = { name, connectors? }
|
|
273
|
-
transport
|
|
274
|
-
|
|
275
|
-
|
|
278
|
+
transport 宣言(funnel.json は名前で宣言するので id はない)。connectors は起動時に
|
|
279
|
+
~/.funnel/projects/<id>/settings.json の該当 Channel に materialize する。コネクタはトークンを
|
|
280
|
+
持たず、CLI か TTY 起動時のプロンプトで設定し、そのスコープ settings に保存される。
|
|
276
281
|
|
|
277
282
|
ProfileSpec = { name, channel, options?, env?, resume? }
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
profiles[] list.
|
|
283
|
+
チャネルに名前でバインドする起動レシピ。`fnl claude --profile <name>` で name 解決して適用され、
|
|
284
|
+
グローバルの profiles[] リストには永続化されない。
|
|
281
285
|
|
|
282
|
-
Settings = { channels[], profiles[] } → ~/.funnel/settings.json
|
|
283
|
-
|
|
286
|
+
Settings = { channels[], profiles[] } → ~/.funnel/settings.json(グローバル)
|
|
287
|
+
または ~/.funnel/projects/<id>/settings.json(リポジトリ単位の funnel.json)
|
|
284
288
|
```
|
|
285
289
|
|
|
286
|
-
|
|
290
|
+
### ファイルレイアウト
|
|
287
291
|
|
|
288
|
-
|
|
292
|
+
永続データは `~/.funnel/` 配下、揮発ログとイベントログは `/tmp/funnel/` 配下に住む。
|
|
289
293
|
|
|
290
294
|
```
|
|
291
295
|
~/.funnel/
|
|
292
|
-
├── settings.json
|
|
296
|
+
├── settings.json グローバルの channels[](nested connectors), profiles[]
|
|
293
297
|
├── projects/
|
|
294
|
-
│ └── <id>/
|
|
295
|
-
│ └── settings.json, gateway.token, claude/, ...
|
|
296
|
-
├── gateway.pid
|
|
297
|
-
├── gateway.token
|
|
298
|
+
│ └── <id>/ funnel.json を持つリポジトリのスコープ state
|
|
299
|
+
│ └── settings.json, gateway.token, claude/, ... (グローバルと同じレイアウト、funnel.json id でスコープ)
|
|
300
|
+
├── gateway.pid デーモン PID
|
|
301
|
+
├── gateway.token デーモン HTTP / WS の Bearer token
|
|
298
302
|
├── claude/
|
|
299
|
-
│ └── <profile-id>.pid
|
|
303
|
+
│ └── <profile-id>.pid 同一プロファイルの二重起動を防ぐ(profile id がキー)
|
|
300
304
|
└── channels/
|
|
301
305
|
└── <channel-id>/
|
|
302
306
|
└── connectors/
|
|
303
307
|
└── <connector-id>/
|
|
304
|
-
└── state.json
|
|
308
|
+
└── state.json コネクタごとの永続 state(例: schedule の lastFiredAt)
|
|
305
309
|
|
|
306
310
|
/tmp/funnel/
|
|
307
|
-
├── events.db
|
|
308
|
-
├── funnel.log
|
|
309
|
-
└── gateway.log
|
|
311
|
+
├── events.db offset 再生付きの SQLite イベントログ
|
|
312
|
+
├── funnel.log 診断ログ(デーモンの起動、listener boot、接続)
|
|
313
|
+
└── gateway.log デーモンの stdout/stderr
|
|
310
314
|
```
|
|
311
315
|
|
|
312
|
-
|
|
316
|
+
コネクタの設定は settings.json にインライン(チャネルの下に nested)で保存され、型ごとのディレクトリには置かない。コネクタごとの永続 state(schedule catch-up の `lastFiredAt` など)は `channels/<channel-id>/connectors/<connector-id>/state.json` に id をキーに住むので、rename しても state を失わない。`fnl gateway logs` は `funnel.log` を tail して YAML として描画する。
|
|
313
317
|
|
|
314
|
-
|
|
315
|
-
- `fnl gateway logs` tails `funnel.log` and renders it as YAML.
|
|
318
|
+
### 環境変数
|
|
316
319
|
|
|
317
|
-
|
|
320
|
+
- `FUNNEL_CHANNEL_ID` — `fnl claude` が子プロセスに注入する。funnel MCP がこれで購読する
|
|
321
|
+
- `FUNNEL_PORT` — gateway ポート。`funnel` CLI 起動はデフォルト 9743、プログラムから起こす gateway は 9742
|
|
322
|
+
- `FUNNEL_GATEWAY_URL` — MCP が WS 購読と HTTP 返信の両方で使うデーモンのベース URL(デフォルト `http://127.0.0.1:<port>`)
|
|
323
|
+
- `FUNNEL_GATEWAY_TOKEN` — デーモン HTTP / WS の Bearer token。デフォルトは `~/.funnel/gateway.token` の中身
|
|
318
324
|
|
|
319
|
-
|
|
320
|
-
| ---------------------- | --------------------------------------------------------------------------------------------- |
|
|
321
|
-
| `FUNNEL_CHANNEL_ID` | Injected into the child process by `fnl claude`; the funnel MCP uses it to subscribe. |
|
|
322
|
-
| `FUNNEL_PORT` | Gateway port. Default 9743 for `funnel` CLI launches, 9742 for a programmatically hosted gateway. |
|
|
323
|
-
| `FUNNEL_GATEWAY_URL` | Daemon base URL used by MCP for both WS subscribe and HTTP reply (default `http://127.0.0.1:<port>`). |
|
|
324
|
-
| `FUNNEL_GATEWAY_TOKEN` | Bearer token for the daemon HTTP / WS. Defaults to the contents of `~/.funnel/gateway.token`. |
|
|
325
|
+
## Discord bot のセットアップ
|
|
325
326
|
|
|
326
|
-
|
|
327
|
+
- Discord Developer Portal で bot を作りトークンを取得する
|
|
328
|
+
- Privileged Gateway Intents の `Message Content Intent` を有効にする
|
|
329
|
+
- OAuth2 → URL Generator で `bot` スコープと `View Channels` / `Send Messages` / `Read Message History` 権限を付けて招待する
|
|
327
330
|
|
|
328
|
-
|
|
329
|
-
- Enable `Message Content Intent` under Privileged Gateway Intents
|
|
330
|
-
- Invite the bot via OAuth2 → URL Generator with the `bot` scope and `View Channels` / `Send Messages` / `Read Message History` permissions
|
|
331
|
+
## プログラマブル API(Bun)
|
|
331
332
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
`funnel` is also usable as a library — the same `Funnel` facade the CLI uses is exported from the package root. The constructor is fully lazy: `new Funnel()` records its props and freezes; no disk / process / network access happens until a method is called.
|
|
333
|
+
CLI を介さず、ライブラリとして組み込める。CLI が使うのと同じ `Funnel` facade をパッケージのルートから export している。コンストラクタは lazy で、`new Funnel()` は props を記録して freeze するだけ。メソッドを呼ぶまでディスク / プロセス / ネットワークに触れない。
|
|
335
334
|
|
|
336
335
|
```ts
|
|
337
336
|
import { Funnel } from "@interactive-inc/claude-funnel"
|
|
338
337
|
|
|
339
|
-
const funnel = new Funnel() //
|
|
340
|
-
|
|
341
|
-
funnel.paths
|
|
342
|
-
// → { dir: "/Users/you/.funnel", tmpDir: "/tmp/funnel", settings: "/Users/you/.funnel/settings.json" }
|
|
338
|
+
const funnel = new Funnel() // ~/.funnel + /tmp/funnel がデフォルト
|
|
343
339
|
|
|
344
340
|
const channel = funnel.channels.add({ name: "inbox" })
|
|
345
|
-
|
|
346
341
|
funnel.channels.addConnector("inbox", {
|
|
347
342
|
type: "slack",
|
|
348
343
|
name: "my-slack",
|
|
349
344
|
botToken: "xoxb-...",
|
|
350
345
|
appToken: "xapp-...",
|
|
351
346
|
})
|
|
352
|
-
|
|
353
|
-
for (const c of funnel.channels.list()) console.log(c.name, c.connectors.length)
|
|
354
347
|
```
|
|
355
348
|
|
|
356
|
-
|
|
349
|
+
`channels` / `profiles` / `gateway` / `listeners` / `mcp` / `claude` など全ファセットが同じインスタンスから辿れる。`gateway` はデーモンの起動・停止、`listeners` は動作中デーモンとの HTTP 会話、`claude` はエージェント起動を担う。
|
|
357
350
|
|
|
358
351
|
```ts
|
|
359
|
-
funnel.gateway.
|
|
360
|
-
|
|
352
|
+
await funnel.gateway.start() // デーモンを別プロセスとして spawn
|
|
353
|
+
funnel.gateway.getStatus() // { running, pid, port }
|
|
361
354
|
|
|
362
|
-
await funnel.listeners.list() // talks to the running daemon over HTTP
|
|
363
355
|
await funnel.listeners.start("inbox", "my-slack")
|
|
364
356
|
await funnel.listeners.restart("inbox", "my-slack")
|
|
365
357
|
|
|
366
|
-
await funnel.claude.launch({ channel: "inbox" })
|
|
367
|
-
funnel.mcp.install("/path/to/repo") // writes .mcp.json
|
|
358
|
+
await funnel.claude.launch({ channel: "inbox" }) // claude を起動(.mcp.json も自動で書く)
|
|
368
359
|
```
|
|
369
360
|
|
|
370
|
-
|
|
361
|
+
デーモンを spawn せず、gateway をインプロセスで動かすこともできる(テストや埋め込み向け)。`onEvent` で全 broadcast イベントをインプロセスで観測できる。
|
|
371
362
|
|
|
372
363
|
```ts
|
|
373
364
|
const server = funnel.gatewayServer({ port: 9742 })
|
|
374
|
-
await server.start()
|
|
365
|
+
await server.start() // Bun.serve (HTTP + WS) + listener supervisor
|
|
375
366
|
const unsubscribe = server.onEvent(({ content, meta }) => {
|
|
376
|
-
console.log(meta?.connector, content)
|
|
367
|
+
console.log(meta?.connector, content)
|
|
377
368
|
})
|
|
378
369
|
await server.stop()
|
|
379
370
|
unsubscribe()
|
|
380
371
|
```
|
|
381
372
|
|
|
382
|
-
|
|
373
|
+
永続化と再生は `FunnelEventLog` port の裏にある。デフォルトは `SqliteFunnelEventLog`(デーモン再起動を跨いで durable。reconnect 時の再生を提供する)。`gatewayServer({ eventLog })` に `MemoryFunnelEventLog` を渡せば durable な再生を差し替え・無効化できる。`onEvent` は書き込み専用の観測フックで、再生(読み戻し)は EventLog の責務。
|
|
383
374
|
|
|
384
|
-
|
|
385
|
-
import { MemoryFunnelEventLog } from "@interactive-inc/claude-funnel"
|
|
375
|
+
### テスト用のサンドボックス
|
|
386
376
|
|
|
387
|
-
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
The daemon exposes `/health`, `/status`, `/listeners*`, `/channels/:channel/connectors/:connector/call`, plus the `/ws?channel=<name>` WebSocket.
|
|
391
|
-
|
|
392
|
-
### Sandboxed Funnel
|
|
393
|
-
|
|
394
|
-
`Funnel.inMemory()` returns a Funnel pre-wired with Memory implementations for every IO boundary — useful for tests and ad-hoc experiments. Pass any subset of `props` to override individual seams:
|
|
377
|
+
`Funnel.inMemory()` は全 IO 境界(ディスク / プロセス / clock / UUID)を Memory 実装で配線済みの Funnel を返す。`props` の任意の部分集合で個々の seam を上書きできるので、実 FS や spawn に触れずにテストを書ける。
|
|
395
378
|
|
|
396
379
|
```ts
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const funnel = Funnel.inMemory() // touches no real disk, processes, clock, or UUIDs
|
|
400
|
-
funnel.channels.add({ name: "inbox" }) // mutates the in-memory store
|
|
380
|
+
const funnel = Funnel.inMemory() // 実ディスク / プロセス / clock / UUID に触れない
|
|
381
|
+
funnel.channels.add({ name: "inbox" }) // インメモリ store を変更する
|
|
401
382
|
```
|
|
402
383
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
```ts
|
|
406
|
-
import {
|
|
407
|
-
Funnel,
|
|
408
|
-
MemoryFunnelClock,
|
|
409
|
-
MemoryFunnelFileSystem,
|
|
410
|
-
MemoryFunnelIdGenerator,
|
|
411
|
-
MemoryFunnelLogger,
|
|
412
|
-
MemoryFunnelProcessRunner,
|
|
413
|
-
MockFunnelSettingsReader,
|
|
414
|
-
} from "@interactive-inc/claude-funnel"
|
|
415
|
-
|
|
416
|
-
const funnel = new Funnel({
|
|
417
|
-
store: new MockFunnelSettingsReader(),
|
|
418
|
-
fs: new MemoryFunnelFileSystem(),
|
|
419
|
-
process: new MemoryFunnelProcessRunner(),
|
|
420
|
-
logger: new MemoryFunnelLogger(),
|
|
421
|
-
clock: new MemoryFunnelClock({ start: new Date("2026-01-01T00:00:00Z") }),
|
|
422
|
-
idGenerator: new MemoryFunnelIdGenerator({ prefix: "test" }),
|
|
423
|
-
dir: "/sandbox/.funnel",
|
|
424
|
-
tmpDir: "/sandbox/tmp",
|
|
425
|
-
})
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
Available abstractions (each has `Funnel*` interface, `Node*` default, and `Memory*` for tests): `FunnelFileSystem`, `FunnelProcessRunner`, `FunnelLogger`, `FunnelClock`, `FunnelIdGenerator`. Plus `NoopFunnelLogger` for silent operation and `MockFunnelSettingsReader` for an in-memory settings store.
|
|
429
|
-
|
|
430
|
-
### Embedding the CLI
|
|
431
|
-
|
|
432
|
-
The same Hono app that backs `fnl` is published as `createCliApp(funnel)` — pass any `Funnel` instance to bind a custom store / boundaries to the routes. The pair `toRequest` (argv → request) and `queryToCliArgs` (URL search params → CLI flags) lets you drive the app programmatically:
|
|
433
|
-
|
|
434
|
-
```ts
|
|
435
|
-
import { Funnel, createCliApp, toRequest } from "@interactive-inc/claude-funnel"
|
|
436
|
-
|
|
437
|
-
const app = createCliApp(Funnel.inMemory())
|
|
438
|
-
const { method, url } = toRequest(["channels", "add", "inbox"])
|
|
439
|
-
const res = await app.request(url, { method })
|
|
440
|
-
console.log(await res.text())
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
`cliApp` is the same app pre-wired to `new Funnel()` for callers who just want the default. The middleware sets the chosen Funnel onto `c.var.funnel`; the matching `Env` type is exported for composing custom routes that share the same context variable.
|
|
444
|
-
|
|
445
|
-
### Validating connector configs
|
|
446
|
-
|
|
447
|
-
Each connector type publishes its Zod schema, so consumers can parse external configs (JSON files, API payloads, etc.) before handing them to `addConnector`. The discriminated union `connectorConfigSchema` covers the whole set.
|
|
448
|
-
|
|
449
|
-
```ts
|
|
450
|
-
import {
|
|
451
|
-
connectorConfigSchema,
|
|
452
|
-
slackConnectorSchema,
|
|
453
|
-
type SlackConnectorConfig,
|
|
454
|
-
} from "@interactive-inc/claude-funnel"
|
|
455
|
-
|
|
456
|
-
const slack: SlackConnectorConfig = slackConnectorSchema.parse(json)
|
|
457
|
-
const any = connectorConfigSchema.parse(json) // narrows by `type`
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
### Packaging
|
|
461
|
-
|
|
462
|
-
The published package ships a bundled library entry (`dist/index.js`) plus generated declarations (`dist/**/*.d.ts`), so consumers do not need a matching tsconfig paths setup to resolve `@/...` imports. The `fnl` / `funnel` bin entries point to a separately bundled `dist/bin.js`. Import `@interactive-inc/claude-funnel/bin` only if you are embedding the CLI binary rather than the library.
|
|
384
|
+
`fnl` を支える Hono アプリ(`createCliApp` / `toRequest`)や、各コネクタの Zod スキーマ(`connectorConfigSchema`)も export している。詳細は型定義を参照。
|
|
463
385
|
|
|
464
386
|
## Claude Code skill
|
|
465
387
|
|
|
466
|
-
|
|
388
|
+
このリポジトリは `.claude/skills/funnel/SKILL.md` に Claude Code skill を同梱している。アーキテクチャとコマンド群を Claude に説明し、フラグレベルの詳細は `funnel <command> --help` に委ねるよう指示する。
|
|
467
389
|
|
|
468
|
-
|
|
390
|
+
プロジェクトスコープ(自動): このリポジトリ内で `claude` を実行すると skill が自動で読まれる。インストール手順は不要。
|
|
469
391
|
|
|
470
|
-
|
|
392
|
+
グローバル(任意のプロジェクトで使う): Claude Code はリモート URL から skill をインストールする CLI を今のところ提供していないので、ファイルを個人の skills ディレクトリへコピーする。
|
|
471
393
|
|
|
472
394
|
```bash
|
|
473
|
-
#
|
|
395
|
+
# このリポジトリの clone から
|
|
474
396
|
mkdir -p ~/.claude/skills/funnel
|
|
475
397
|
cp .claude/skills/funnel/SKILL.md ~/.claude/skills/funnel/
|
|
476
398
|
```
|
|
477
399
|
|
|
478
|
-
|
|
400
|
+
clone せず直接取得することもできる。
|
|
479
401
|
|
|
480
402
|
```bash
|
|
481
403
|
mkdir -p ~/.claude/skills/funnel
|
|
@@ -483,31 +405,31 @@ curl -fsSL https://raw.githubusercontent.com/interactive-inc/open-claude-funnel/
|
|
|
483
405
|
-o ~/.claude/skills/funnel/SKILL.md
|
|
484
406
|
```
|
|
485
407
|
|
|
486
|
-
|
|
408
|
+
これで Claude Code がどのセッションでも skill を読む。
|
|
487
409
|
|
|
488
|
-
##
|
|
410
|
+
## 開発
|
|
489
411
|
|
|
490
412
|
```bash
|
|
491
413
|
git clone https://github.com/interactive-inc/open-claude-funnel.git
|
|
492
414
|
cd open-claude-funnel
|
|
493
|
-
bun install #
|
|
494
|
-
make build #
|
|
495
|
-
bun link #
|
|
496
|
-
make build #
|
|
497
|
-
make build-lib #
|
|
498
|
-
make build-bin # CLI / daemon
|
|
499
|
-
make clean #
|
|
500
|
-
bun test #
|
|
501
|
-
bunx tsc -b #
|
|
502
|
-
bun lib/bin.ts ... #
|
|
415
|
+
bun install # 依存インストール(自動ビルドなし)
|
|
416
|
+
make build # dist/ を生成 — install 後に一度実行
|
|
417
|
+
bun link # fnl / funnel → dist/bin.js を symlink
|
|
418
|
+
make build # 編集後にライブラリ + CLI を再ビルド
|
|
419
|
+
make build-lib # ライブラリのみ(vp pack)
|
|
420
|
+
make build-bin # CLI / daemon のみ(bun build --minify)
|
|
421
|
+
make clean # dist/ を削除
|
|
422
|
+
bun test # テスト実行
|
|
423
|
+
bunx tsc -b # 型チェック
|
|
424
|
+
bun lib/bin.ts ... # ソースから CLI を実行(ビルド不要、高速イテレーション)
|
|
503
425
|
```
|
|
504
426
|
|
|
505
|
-
##
|
|
427
|
+
## リンク
|
|
506
428
|
|
|
507
429
|
- [GitHub](https://github.com/interactive-inc/open-claude-funnel)
|
|
508
430
|
- [Issues](https://github.com/interactive-inc/open-claude-funnel/issues)
|
|
509
|
-
-
|
|
431
|
+
- コーディング規約と設計原則: [CLAUDE.md](https://github.com/interactive-inc/open-claude-funnel/blob/main/CLAUDE.md)
|
|
510
432
|
|
|
511
|
-
##
|
|
433
|
+
## ライセンス
|
|
512
434
|
|
|
513
435
|
MIT © Interactive Inc.
|