@interactive-inc/claude-funnel 0.35.0 → 0.37.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 CHANGED
@@ -1,97 +1,53 @@
1
1
  [![npm](https://img.shields.io/npm/v/@interactive-inc/claude-funnel.svg)](https://www.npmjs.com/package/@interactive-inc/claude-funnel)
2
2
  [![license](https://img.shields.io/npm/l/@interactive-inc/claude-funnel.svg)](./LICENSE)
3
3
 
4
- A hub for AI coding agents. One long-running daemon owns all external connections; agents subscribe to named channels and react to events without you wiring up shell scripts and cron entries. Outbound replies travel back through the same connectors as MCP tools, so answering a message or commenting on an issue does not need a bash subshell.
4
+ # Open Claude Funnel
5
5
 
6
- The command is `funnel` (or the shorthand `fnl`).
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
- external sources outbound replies
26
- (chat / source-control / cron) (MCP tools per connector)
27
- │ │
28
- ▼ ▼
29
- daemon (port 9742)
30
- routes events into channels
31
- serves replies through the same connectors
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
- Two concepts make up the transport model:
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
- Profile sits on top of that model as a launch convenience, not part of it — you never need one to run an agent (`fnl claude --channel <name>` is enough). It is a saved launch preset bundling `{ path, channelId, options, env, resume }` so `fnl claude --profile cto` reproduces a known setup: which directory to launch from, which channel to bind, and the launch recipe (args prepended to the claude argv, env layered under the process, session reuse). A profile carries a stable uuid `id` (the unit the PID file and the resumable session key off, so renaming it strands neither); `name` is just the handle you type. The first profile in the list is the default. Because a profile already binds a channel, `--profile` and `--channel` cannot be combined.
21
+ ## funnel がやること
44
22
 
45
- The daemon is where all external connections live. It runs on port 9743 for `funnel` CLI launches (9742 for a gateway hosted programmatically, so the two never collide on one machine — `FUNNEL_PORT` overrides either), bound to loopback (`127.0.0.1`) only so it is never reachable off-box; set `FUNNEL_HOST=0.0.0.0` to expose it deliberately (every privileged endpoint still requires the bearer token regardless). It supervises connectors with auto-restart, broadcasts events to subscribed agent sessions over WebSocket, and serves the reply API that MCP calls. Starting or stopping an agent never starts or stops external connections.
23
+ - 外部の出来事(チャットのメンション、新しい Issue、cron tick)を、起動中のエージェントセッションにそのまま届ける
24
+ - エージェントの返信を受信と同じコネクタで外へ返す。bash サブシェルも CLI のコールドスタートもなく、実質同期
25
+ - 外部接続は常駐デーモンが 1 か所で持つ。エージェントを何個起こしても接続は 1 回だけ。ヘルスチェックと自動再起動で監視する
26
+ - 複数のエージェントで 1 つのソースを共有(fanout)するか、ワーカーとして分担(exclusive)するかを選べる
46
27
 
47
- The MCP layer is a thin bridge into the agent. It subscribes to the bound channel over WebSocket (the daemon does the work) and exposes one tool per callable connector so the agent can reply back out.
28
+ 対応コネクタは Slack(Socket Mode)、GitHub(`gh` 経由の poll)、Discord(Gateway)、cron スケジュールの 4 種類。
48
29
 
49
- ## Requirements
30
+ ## 必要なもの
50
31
 
51
- - [Bun](https://bun.sh) 1.3 or later
32
+ - [Bun](https://bun.sh) 1.3 以降
52
33
  - [Claude Code](https://docs.claude.com/en/docs/claude-code) CLI
53
- - A token or CLI for whichever external service you connect (Slack app, `gh` auth, Discord bot, etc.)
34
+ - 接続する外部サービスのトークンまたは CLISlack アプリ、`gh` 認証、Discord bot など)
54
35
 
55
- ## Install
36
+ ## リポジトリで使う(推奨)
56
37
 
57
- Per-repo (recommended for getting started — no global install, version pinned by the repo's lock file):
38
+ 設定を `funnel.json` としてリポジトリに commit し、チームで共有・バージョン管理できる。グローバルに何も入れないので、リポジトリの lock ファイルでバージョンが固定される。
58
39
 
59
- ```bash
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 -g @interactive-inc/claude-funnel
68
- funnel claude # or the `fnl` shorthand
43
+ bun add -D @interactive-inc/claude-funnel
69
44
  ```
70
45
 
71
- The published package ships the built `dist/`, so either install makes `funnel` / `fnl` available immediately — no post-install step. The rest of the README uses `fnl` for brevity; swap in `bunx funnel` if you went the per-repo route.
46
+ これで `bunx funnel`(または `bunx fnl`)が使える。
72
47
 
73
- ## Quick start
48
+ ### 設定
74
49
 
75
- Wire one source to one agent:
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
  {
@@ -100,10 +56,7 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
100
56
  {
101
57
  "name": "ops",
102
58
  "connectors": [
103
- {
104
- "type": "slack",
105
- "name": "my-slack"
106
- }
59
+ { "type": "slack", "name": "my-slack" }
107
60
  ]
108
61
  },
109
62
  {
@@ -112,11 +65,14 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
112
65
  ],
113
66
  "profiles": [
114
67
  {
68
+ "name": "pm",
115
69
  "channel": "ops",
116
70
  "options": ["--brief", "--agent", "pm"],
117
- "env": { "ANTHROPIC_MODEL": "claude-sonnet-4-6" }
71
+ "env": { "ANTHROPIC_MODEL": "claude-sonnet-4-6" },
72
+ "resume": true
118
73
  },
119
74
  {
75
+ "name": "reviewer",
120
76
  "channel": "review",
121
77
  "options": ["--agent", "reviewer"]
122
78
  }
@@ -124,114 +80,165 @@ Or drop a `funnel.json` in the repo and `fnl claude` (no args) inside the repo w
124
80
  }
125
81
  ```
126
82
 
127
- `channels[]` is required and the first entry is the default. `fnl claude --channel review` picks one by name; `fnl claude` with no `--channel` uses the first.
83
+ channels は購読箱(transport)。各チャネルは `connectors` を持てる。`connectors` 配列はそのチャネルの真実の源で、起動時に宣言にないコネクタは削除され、足りないコネクタは作られる(名前で照合)。`connectors` フィールド自体が無ければ既存のコネクタはそのまま残る。
84
+
85
+ profiles は起動レシピ。各プロファイルは一意な `name` を持ち、`channel` でチャネルを名前指定でバインドし、次を持つ。
128
86
 
129
- A channel declares only transport — its `connectors` and delivery mode. The launch recipe lives on `profiles[]`: each profile has a unique `name`, binds to a channel by name, and carries `options` (prepended to the claude argv before user-supplied CLI args, which still come last — use it for flags like `--brief`, `--agent <name>`, `--model <name>`), `env` (layered under the launched claude process — `process.env` from the launching shell wins on collision), and `resume`. A profile is launched by name with `fnl claude --profile <name>`; the channel never selects a profile on its own. Multiple profiles may bind the same channel — they are told apart by `name`.
87
+ - `options` claude argv 先頭に積む(ユーザーが渡す CLI 引数はその後ろ。`--brief` / `--agent <name>` / `--model <name>` などに使う)
88
+ - `env` — 起動する claude プロセスに被せる(衝突時は起動シェルの `process.env` が勝つ)
89
+ - `resume` — claude セッションの再利用可否
130
90
 
131
- The optional `connectors` array on a channel is the source of truth for that channel: missing connectors are created and connectors not declared are removed on launch. Connectors are matched by name. An absent `connectors` field leaves existing connectors alone.
91
+ 同じチャネルに複数のプロファイルをバインドしてよく、`name` で区別する。チャネル側がプロファイルを選ぶことはない(プロファイルがチャネルを bind する一方向)。
132
92
 
133
- A repo with a `funnel.json` is scoped to itself. On first launch funnel writes a stable `id` (uuid) to the top of `funnel.json` and keeps every byte of funnel state under `~/.funnel/projects/<id>/`, never touching the global `~/.funnel` — only the event log and temp files under `/tmp/funnel/` are shared. This scoping applies to every CLI command run in the repo, not just `fnl claude`. On launch the chosen channel's transport (`connectors`, delivery) is materialized into `~/.funnel/projects/<id>/settings.json`; the profile recipe is passed straight to the launcher and is not persisted there. Raw launches (`fnl claude --channel <name>` without funnel.json) bind transport only against the global `~/.funnel` and carry no recipe.
93
+ トークンは `funnel.json` に書かない(スキーマが弾く)。リポジトリ内で次のどちらか。
134
94
 
135
- The optional top-level `$schema` points at the JSON Schema so editors can validate and autocomplete the file. The recommended reference for repos with a local install is `./node_modules/@interactive-inc/claude-funnel/funnel.schema.json` — it works without a network round-trip and editors do not need to prompt for trust. The same file is also published at `https://interactive-inc.github.io/open-claude-funnel/funnel.schema.json` (editors usually require explicit trust on first use), and `fnl schema > funnel.schema.json` regenerates a local copy on demand.
95
+ - `fnl channels ops connectors set my-slack --bot-token=xoxb-... --app-token=xapp-...` で設定する
96
+ - 省略して `fnl claude` 起動時の TTY プロンプトで答える。次回以降は引き継がれ再度聞かれない
136
97
 
137
- Connectors in `funnel.json` carry no tokens. A connector's token is set out of band and stored only in the repo-scoped settings:
98
+ いずれもトークンは `~/.funnel/projects/<id>/settings.json`(リポジトリ外)に保存され、commit されない。
138
99
 
139
- - run `fnl channels <ch> connectors set <c> --bot-token=...` (etc.) in the repo to write it to `~/.funnel/projects/<id>/settings.json`
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
100
+ `funnel.json` に書けないものが 2 つある。delivery モード(fanout / exclusive)は CLI で設定する(`funnel.json` のチャネルは fanout 固定)。schedule cron エントリも CLI で足す(`funnel.json` には schedule コネクタの存在だけ宣言できる)。
141
101
 
142
- Only the `id` is ever written back to `funnel.json`; tokens never live in the repo.
102
+ セッション再開には `resume` の明示が要る。`funnel.json``resume` はデフォルト値を持たず、書かないと再開されない(未指定が false 扱いになる)。前回の claude セッションを引き継ぎたいなら必ず `"resume": true` を書く。再開のための session id funnel が `~/.funnel/projects/<id>/settings.json` に自動で保存・読み出しするので、`funnel.json` には書かない。
143
103
 
144
- Cron-driven agent runs:
104
+ ### 使い方
145
105
 
146
106
  ```bash
147
- fnl channels ops connectors add daily --type=schedule
148
- fnl channels ops connectors daily schedules add morning \
107
+ bunx fnl gateway start # 常駐デーモンを起動(外部接続を持つ)
108
+ bunx fnl claude --profile pm # cd + チャネルのバインド + レシピを一発で
109
+ ```
110
+
111
+ `--profile` なしの `bunx fnl claude` は、`funnel.json` の先頭チャネルで起動する。`--channel review` で名前指定すると、そのチャネルを transport だけバインドして起動する(レシピなし)。
112
+
113
+ これでコネクタが見たイベントが、起動中のエージェントセッションに届く。エージェントは `my-slack` という MCP ツールで返信できる。
114
+
115
+ cron 起動を足す(schedule コネクタを宣言したうえで、エントリは CLI で)。
116
+
117
+ ```bash
118
+ bunx fnl channels ops connectors add daily --type=schedule
119
+ bunx fnl channels ops connectors daily schedules add morning \
149
120
  --cron="0 9 * * *" --prompt="morning standup"
150
121
  ```
151
122
 
152
- Each tick fires the prompt into the channel. If the daemon was down at 9 AM, the next start catches up the missed slot (`meta.catchup = "true"`) for up to 24 hours.
123
+ tick ごとにプロンプトがチャネルへ発火する。9 時にデーモンが落ちていても、次回起動時に逃した枠を catch-up する(`meta.catchup = "true"`、最大 24 時間)。
124
+
125
+ `funnel.json` を持つリポジトリは自分自身にスコープされる。初回起動時に funnel は `funnel.json` の先頭へ不変の `id`(uuid) を書き戻し、以降このリポジトリの funnel state を `~/.funnel/projects/<id>/` 配下に隔離する。グローバルの `~/.funnel` には一切触らない(イベントログと一時ファイルだけが `/tmp/funnel/` で共有)。このスコープはリポジトリ内で実行する全 CLI コマンドに効く。
126
+
127
+ トップレベルの `$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
128
 
154
- Multiple agents on the same source — pick the delivery mode:
129
+ ## グローバルで使う
130
+
131
+ 触るすべてのリポジトリで 1 つの CLI を共有したいときはグローバルに入れる。設定は `funnel.json` ではなく CLI で組み、`~/.funnel/settings.json`(グローバル)に保存される。
155
132
 
156
133
  ```bash
157
- fnl channels add reviews # fanout (default): every agent sees every event
158
- fnl channels add ingest --delivery=exclusive # exclusive: one event per agent, round-robin
134
+ bun add -g @interactive-inc/claude-funnel
135
+ ```
136
+
137
+ これで `funnel` / `fnl` がどこでも使える。ソース 1 つをエージェント 1 つにつなぐ最短手順。
138
+
139
+ ```bash
140
+ fnl channels add ops
141
+ fnl channels ops connectors add my-slack --type=slack \
142
+ --bot-token=xoxb-... --app-token=xapp-...
143
+ fnl gateway start
144
+ fnl claude --channel ops
145
+ ```
146
+
147
+ delivery モードはチャネル作成時に選ぶ。
148
+
149
+ ```bash
150
+ fnl channels add reviews # fanout(デフォルト): 全エージェントが全イベント
151
+ fnl channels add ingest --delivery=exclusive # exclusive: 1 イベントを 1 エージェントが round-robin
152
+ ```
153
+
154
+ ワンコマンド起動のためにプロファイルとして保存する。プロファイルは起動レシピを持つ。
155
+
156
+ ```bash
157
+ fnl profiles add cto --path=/repo/myapp --channel=ops --agent=pm --options="--brief"
158
+ fnl claude --profile cto
159
159
  ```
160
160
 
161
- ## CLI surface
162
-
163
- Connectors live nested inside their owning channel. Every write verb (`add` / `set` / `remove` / `rename` / `as-default` / `request`) maps to `POST` plus the verb in the URL — the same word stays visible in shell and HTTP form. Read paths stay `GET`.
164
-
165
- ```text
166
- fnl channels list
167
- fnl channels add <name> [--delivery=fanout|exclusive]
168
- fnl channels <name> show details
169
- fnl channels remove <name>
170
- fnl channels rename <old> <new> (also `fnl channels <old> rename <new>`)
171
- fnl channels <name> set delivery <mode>
172
-
173
- fnl channels <ch> connectors list
174
- fnl channels <ch> connectors add <c> --type=slack --bot-token=xoxb-... --app-token=xapp-...
175
- fnl channels <ch> connectors add <c> --type=gh [--poll-interval=<sec>]
176
- fnl channels <ch> connectors add <c> --type=discord --bot-token=<token>
177
- fnl channels <ch> connectors add <c> --type=schedule
178
- fnl channels <ch> connectors <c> show config
179
- fnl channels <ch> connectors set <c> [--bot-token=...] [--app-token=...] [--poll-interval=...]
180
- fnl channels <ch> connectors remove <c>
181
- fnl channels <ch> connectors rename <c> <new>
182
- fnl channels <ch> connectors <c> request --method=<api.method> [--key=value ...]
183
-
184
- fnl channels <ch> connectors <c> schedules list cron entries
185
- fnl channels <ch> connectors <c> schedules add <id> --cron="<expr>" --prompt="<text>" \
186
- [--enabled=true] [--catchup-policy=latest|all|skip]
187
- fnl channels <ch> connectors <c> schedules remove <id>
188
-
189
- fnl profiles list (first entry is the default)
190
- fnl profiles add <name> --path=<dir> --channel=<channel-name> \
191
- [--agent=<name>] [--options="<argv>"] [--env="K=V,K2=V2"] [--no-resume]
192
- fnl profiles <name> launch (alias for `<name> run`)
193
- fnl profiles <name> run launch (sugar for `fnl claude --profile <name>`)
194
- fnl profiles <name> set [--path=...] [--channel=...] \
195
- [--agent=...] [--options="..."] [--env="..."] [--resume|--no-resume]
196
- fnl profiles <name> as-default move to the front of the list
197
- fnl profiles rename <old> <new>
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
161
+ 公開パッケージはビルド済みの `dist/` を同梱しているので、どちらのインストールでも `funnel` / `fnl` がすぐ使える(post-install ステップなし)。リポジトリ単位で入れたなら、以降の `fnl` は `bunx funnel` に読み替える。
162
+
163
+ コマンドの一覧やフラグはここに並べない。`fnl --help` で全体、`fnl <command> --help`(例 `fnl channels --help`)で個別の使い方が出る。動詞だけを引数なしで打ってもヘルプが返る。
164
+
165
+ ## なぜ funnel なのか
166
+
167
+ 1 つのエージェントセッションは、1 つのリポジトリ・1 つの瞬間を扱うのは得意だ。だがそれに「何かに反応させたい」と思った瞬間 — チャットのメンション、新しい Issue、朝 9 時のスタンドアップ — シェルスクリプトと cron と `bash -c "agent ..."` をつなぎ合わせる羽目になる。「誰が何を聞いていて、誰がどこに返信していいのか」を一望できる場所がどこにもない。
168
+
169
+ funnel はその場所になる。名前付きの購読箱(チャネル)を宣言し、コネクタを取り付け、チャネルをバインドしてエージェントを起動すれば、あとはデーモンが面倒を見る。使うモチベーションは次の点にある。
170
+
171
+ 外部接続をデーモンが 1 か所で持つ。各接続は、エージェントセッションを何個起こしても 1 回だけつながる。2 つ目のエージェントが 2 つ目のソケットを開くことはなく、両方が同じチャネルを購読し、デーモンがイベントを fanout する。
172
+
173
+ 受信イベントは MCP 通知として届くので、エージェントは今いるセッションのまま反応する。新しいプロセスを起こさない。
174
+
175
+ 送信(返信)はコネクタごとの MCP ツールを使う。bash CLI のコールドスタートもなく、実質同期で返る。
176
+
177
+ リスナーはヘルスチェックと自動再起動で監視される。接続が不安定でも poller がクラッシュしても、自分で復旧する。
178
+
179
+ 複数のエージェントが 1 つのチャネルを共有する(fanout)か、ワーカーとしてイベントを取り合う(exclusive)かを選べる。どのイベントを誰が受けるかはデーモンが決める。
180
+
181
+ つまり、外部とのつなぎ込みという「配管」を 1 つのデーモンに集約し、エージェント側は購読と返信という単純な世界だけを見ればよくなる。これが funnel を使う理由になる。
182
+
183
+ ## 仕組み
184
+
185
+ ### 全体像
186
+
187
+ ```
188
+ external sources outbound replies
189
+ (chat / source-control / cron) (MCP tools per connector)
190
+ │ │
191
+ ▼ ▼
192
+ daemon (port 9743 for CLI)
193
+ routes events into channels
194
+ serves replies through the same connectors
195
+
196
+ ▼ WebSocket / MCP (stdio)
197
+ agent (subscribes to one channel)
220
198
  ```
221
199
 
222
- `--channel` accepts the channel name (not the uuid). The CLI resolves it to a channel id before calling the engine.
200
+ ### Channel Connector Profile
201
+
202
+ transport モデルは 2 つの概念でできている。
203
+
204
+ Channel は名前付きの購読箱(transport のみ)。1 つ以上のコネクタと delivery モードを持ち、起動フラグは持たない。エージェントセッションはちょうど 1 つのチャネルを購読する。delivery は `fanout`(全 subscriber が全イベントを見る、デフォルト)か `exclusive`(1 イベントを 1 subscriber が round-robin で消費、ワーカープール向け)。
205
+
206
+ Connector はチャネルから外部ソースへの 1 つの接続。`slack` / `gh` / `discord` / `schedule` の 4 型。前者 3 つは双方向(イベント入力・返信出力)、`schedule` は一方向(cron tick の入力のみ)。
223
207
 
224
- ## Outbound calls (MCP tools per connector)
208
+ 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
209
 
226
- When `fnl claude` launches the agent, the funnel MCP server connects to the daemon and reads the channel's connectors from `~/.funnel/settings.json`. For every callable connector (`slack` / `discord` / `gh`; `schedule` is one-way and skipped), MCP advertises one tool with the connector's name. The agent calls them like:
210
+ ### daemon
211
+
212
+ 外部接続はすべてデーモンに住む。`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 を提供する。エージェントの起動・停止が外部接続を起動・停止することはない。
213
+
214
+ ### MCP
215
+
216
+ MCP レイヤはエージェントへの薄いブリッジ。バインドされたチャネルを WebSocket で購読し(実作業はデーモンがやる)、呼び出し可能なコネクタごとに 1 つのツールを公開して、エージェントが外へ返信できるようにする。
217
+
218
+ ### イベントの旅
219
+
220
+ 1 つの Slack メッセージがエージェントに届くまで。
221
+
222
+ ```
223
+ Slack → SlackListener.start(notify) → notify(channel, connector, content, meta)
224
+ → GatewayServer.notify → Broadcaster.broadcast → event store に seq 付き保存
225
+ → 該当 Channel を購読している WS クライアントに fanout
226
+ → エージェント側 MCP(channel-server)が受信してエージェントに events として渡す
227
+ ```
228
+
229
+ 逆方向(エージェント → Slack)は MCP のコネクタごとの tool 経由。Listener と Adapter は独立した一方向の通路で、Broadcaster は経由しない。
230
+
231
+ ### 外部への送信
232
+
233
+ `fnl claude` がエージェントを起動すると、funnel の MCP サーバがデーモンに接続し、チャネルのコネクタを `~/.funnel/settings.json` から読む。呼び出し可能なコネクタ(`slack` / `discord` / `gh`。`schedule` は一方向なので除外)ごとに、コネクタ名のツールを 1 つ公開する。エージェントはこう呼ぶ。
227
234
 
228
235
  ```jsonc
229
- // MCP: tools/list returns
236
+ // MCP: tools/list が返す
230
237
  { "name": "discord", "inputSchema": { ... { method, path, body } ... } }
231
238
  { "name": "ops-slack", "inputSchema": { ... } }
232
239
  { "name": "gh-main", "inputSchema": { ... } }
233
240
 
234
- // agent calls
241
+ // エージェントの呼び出し
235
242
  tools/call name="discord" arguments={
236
243
  "method": "POST",
237
244
  "path": "/channels/123/messages",
@@ -239,243 +246,160 @@ tools/call name="discord" arguments={
239
246
  }
240
247
  ```
241
248
 
242
- MCP forwards via HTTP `POST /channels/<channel>/connectors/<connector>/call` to the daemon, which dispatches through the connector's adapter. No bash subshell, no CLI cold start — replies are essentially synchronous.
249
+ MCP HTTP `POST /channels/<channel>/connectors/<connector>/call` でデーモンへ転送し、デーモンがコネクタの adapter 経由でディスパッチする。bash サブシェルもコールドスタートもなく、返信は実質同期。
243
250
 
244
- To invoke a connector from outside an agent, the same path is reachable as `fnl channels <ch> connectors <c> request --method=<...> [--key=value ...]`.
251
+ エージェントの外からコネクタを呼ぶには、同じパスを `fnl channels <ch> connectors <c> request --method=<...> [--key=value ...]` で叩ける。
245
252
 
246
- ## Data model
253
+ ### データモデル
247
254
 
248
255
  ```
249
256
  Channel = { id, name, delivery, connectors[] }
250
- subscription box (transport only). delivery is `fanout` (every WS client sees every event)
251
- or `exclusive` (round-robin one client per event). carries no launch settings.
257
+ 購読箱(transport のみ)。delivery `fanout`(全 WS クライアントが全イベント受信)
258
+ `exclusive`(round-robin 1 イベント 1 クライアント)。起動設定は持たない。
252
259
 
253
260
  Connector =
254
261
  | { type: "slack", name, botToken, appToken } Slack Socket Mode
255
- | { type: "gh", name, pollInterval? } GitHub (gh CLI, poll-based)
262
+ | { type: "gh", name, pollInterval? } GitHubgh CLI, poll ベース)
256
263
  | { type: "discord", name, botToken } Discord Gateway
257
- | { type: "schedule", name, entries[] } cron-driven; entries = { id, cron, prompt, enabled?, catchupPolicy? }
264
+ | { type: "schedule", name, entries[] } cron 起動。entries = { id, cron, prompt, enabled?, catchupPolicy? }
258
265
 
259
266
  Profile = { id, name, path, channelId, options[], env, resume, sessionId? }
260
- named launch preset: where to launch (path), which channel to bind, and the launch recipe —
261
- options[] prepends to the claude argv, env layers under the process (process.env wins on
262
- collision), resume toggles session reuse. the first profile is the default. id is a stable
263
- uuid (the key the PID file and resumable session hang off, so a rename strands neither);
264
- name is the CLI handle. sessionId is execution state, not config the claude session this
265
- profile last launched, written by the launcher and read back on the next resume.
267
+ 名前付き起動 preset: どこで起動するか(path)、どのチャネルをバインドするか、起動レシピ。
268
+ options[] claude argv の先頭に積み、env はプロセスに被せ(衝突時は process.env が勝つ)、
269
+ resume はセッション再利用を切り替える。先頭がデフォルト。id は不変の uuid(PID ファイルと
270
+ 再開可能セッションがぶら下がるキー。rename でどちらも迷子にしない)、name CLI のハンドル。
271
+ sessionId config ではなく実行状態で、このプロファイルが最後に起動した claude セッション。
272
+ launcher が書き、次回 resume で読み戻す。
266
273
 
267
274
  LocalConfig = { id?, channels: ChannelSpec[], profiles?: ProfileSpec[] }
268
- per-repo file (funnel.json). channels[] required; first entry is default, --channel selects.
269
- id (uuid) is written back on first launch; this repo's funnel state lives under
270
- ~/.funnel/projects/<id>/ and the global ~/.funnel is never touched.
275
+ リポジトリ単位のファイル(funnel.json)。channels[] 必須、先頭がデフォルト、--channel で選ぶ。
276
+ iduuid)は初回起動時に書き戻され、このリポジトリの funnel state
277
+ ~/.funnel/projects/<id>/ 配下に住み、グローバルの ~/.funnel には一切触れない。
271
278
 
272
279
  ChannelSpec = { name, connectors? }
273
- transport declaration (no id, since funnel.json declares by name). connectors materialize into
274
- the matching Channel in ~/.funnel/projects/<id>/settings.json on launch. Connectors carry no
275
- tokens; a token is set via the CLI or prompted on a TTY at launch and saved to that scoped settings.
280
+ transport 宣言(funnel.json は名前で宣言するので id はない)。connectors は起動時に
281
+ ~/.funnel/projects/<id>/settings.json の該当 Channel materialize する。コネクタはトークンを
282
+ 持たず、CLI TTY 起動時のプロンプトで設定し、そのスコープ settings に保存される。
276
283
 
277
284
  ProfileSpec = { name, channel, options?, env?, resume? }
278
- launch recipe bound to a channel by name. applied inline on launch (the first spec bound to the
279
- chosen channel — selected by its channel binding, not by name); not persisted into the global
280
- profiles[] list.
285
+ チャネルに名前でバインドする起動レシピ。`fnl claude --profile <name>` name 解決して適用され、
286
+ グローバルの profiles[] リストには永続化されない。
281
287
 
282
- Settings = { channels[], profiles[] } → ~/.funnel/settings.json (global)
283
- or ~/.funnel/projects/<id>/settings.json (per-repo funnel.json)
288
+ Settings = { channels[], profiles[] } → ~/.funnel/settings.json(グローバル)
289
+ または ~/.funnel/projects/<id>/settings.json(リポジトリ単位の funnel.json
284
290
  ```
285
291
 
286
- ## File layout
292
+ ### ファイルレイアウト
287
293
 
288
- Persistent state lives under `~/.funnel/`. Volatile logs and the event log live under `/tmp/funnel/`.
294
+ 永続データは `~/.funnel/` 配下、揮発ログとイベントログは `/tmp/funnel/` 配下に住む。
289
295
 
290
296
  ```
291
297
  ~/.funnel/
292
- ├── settings.json global channels[] with nested connectors, profiles[]
298
+ ├── settings.json グローバルの channels[]nested connectors), profiles[]
293
299
  ├── projects/
294
- │ └── <id>/ per-repo state for a repo with funnel.json
295
- │ └── settings.json, gateway.token, claude/, ... (same layout as the global root, scoped by funnel.json id)
296
- ├── gateway.pid daemon PID
297
- ├── gateway.token Bearer token for daemon HTTP / WS
300
+ │ └── <id>/ funnel.json を持つリポジトリのスコープ state
301
+ │ └── settings.json, gateway.token, claude/, ... (グローバルと同じレイアウト、funnel.json id でスコープ)
302
+ ├── gateway.pid デーモン PID
303
+ ├── gateway.token デーモン HTTP / WS Bearer token
298
304
  ├── claude/
299
- │ └── <profile-id>.pid prevents double-launch of the same profile (keyed by profile id)
305
+ │ └── <profile-id>.pid 同一プロファイルの二重起動を防ぐ(profile id がキー)
300
306
  └── channels/
301
307
  └── <channel-id>/
302
308
  └── connectors/
303
309
  └── <connector-id>/
304
- └── state.json per-connector durable state (e.g. schedule lastFiredAt)
310
+ └── state.json コネクタごとの永続 state(例: schedule lastFiredAt
305
311
 
306
312
  /tmp/funnel/
307
- ├── events.db SQLite event log with replay-by-offset
308
- ├── funnel.log diagnostic log (daemon lifecycle, listener boot, connects)
309
- └── gateway.log daemon stdout/stderr
313
+ ├── events.db offset 再生付きの SQLite イベントログ
314
+ ├── funnel.log 診断ログ(デーモンの起動、listener boot、接続)
315
+ └── gateway.log デーモンの stdout/stderr
310
316
  ```
311
317
 
312
- Notes:
318
+ コネクタの設定は 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
319
 
314
- - Connector configuration is stored inline in `settings.json` (nested under the channel), not in a per-type directory. Per-connector durable state (e.g. `lastFiredAt` for schedule catch-up) lives under `channels/<channel-id>/connectors/<connector-id>/state.json` keyed by id, so renames do not lose state.
315
- - `fnl gateway logs` tails `funnel.log` and renders it as YAML.
320
+ ### 環境変数
316
321
 
317
- ## Environment variables
322
+ - `FUNNEL_CHANNEL_ID` — `fnl claude` が子プロセスに注入する。funnel MCP がこれで購読する
323
+ - `FUNNEL_PORT` — gateway ポート。`funnel` CLI 起動はデフォルト 9743、プログラムから起こす gateway は 9742
324
+ - `FUNNEL_GATEWAY_URL` — MCP が WS 購読と HTTP 返信の両方で使うデーモンのベース URL(デフォルト `http://127.0.0.1:<port>`)
325
+ - `FUNNEL_GATEWAY_TOKEN` — デーモン HTTP / WS の Bearer token。デフォルトは `~/.funnel/gateway.token` の中身
318
326
 
319
- | Variable | Purpose |
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://localhost:9742`). |
324
- | `FUNNEL_GATEWAY_TOKEN` | Bearer token for the daemon HTTP / WS. Defaults to the contents of `~/.funnel/gateway.token`. |
327
+ ## Discord bot のセットアップ
325
328
 
326
- ## Discord bot setup
329
+ - Discord Developer Portal で bot を作りトークンを取得する
330
+ - Privileged Gateway Intents の `Message Content Intent` を有効にする
331
+ - OAuth2 → URL Generator で `bot` スコープと `View Channels` / `Send Messages` / `Read Message History` 権限を付けて招待する
327
332
 
328
- - Create a bot in the Discord Developer Portal and obtain its token
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
333
+ ## プログラマブル API(Bun)
331
334
 
332
- ## Programmable API (Bun)
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.
335
+ CLI を介さず、ライブラリとして組み込める。CLI が使うのと同じ `Funnel` facade をパッケージのルートから export している。コンストラクタは lazy で、`new Funnel()` は props を記録して freeze するだけ。メソッドを呼ぶまでディスク / プロセス / ネットワークに触れない。
335
336
 
336
337
  ```ts
337
338
  import { Funnel } from "@interactive-inc/claude-funnel"
338
339
 
339
- const funnel = new Funnel() // defaults to ~/.funnel + /tmp/funnel on the local filesystem
340
-
341
- funnel.paths
342
- // → { dir: "/Users/you/.funnel", tmpDir: "/tmp/funnel", settings: "/Users/you/.funnel/settings.json" }
340
+ const funnel = new Funnel() // ~/.funnel + /tmp/funnel がデフォルト
343
341
 
344
342
  const channel = funnel.channels.add({ name: "inbox" })
345
-
346
343
  funnel.channels.addConnector("inbox", {
347
344
  type: "slack",
348
345
  name: "my-slack",
349
346
  botToken: "xoxb-...",
350
347
  appToken: "xapp-...",
351
348
  })
352
-
353
- for (const c of funnel.channels.list()) console.log(c.name, c.connectors.length)
354
349
  ```
355
350
 
356
- Every facet — `channels` / `profiles` / `gateway` / `gatewayServer` / `gatewayToken` / `listeners` / `mcp` / `claude` / `factory` / `store` / `process` / `logger` / `paths` — is reachable from the same instance:
351
+ `channels` / `profiles` / `gateway` / `listeners` / `mcp` / `claude` など全ファセットが同じインスタンスから辿れる。`gateway` はデーモンの起動・停止、`listeners` は動作中デーモンとの HTTP 会話、`claude` はエージェント起動を担う。
357
352
 
358
353
  ```ts
359
- funnel.gateway.getStatus() // { running, pid, port }
360
- await funnel.gateway.start() // spawns the daemon as a separate process
354
+ await funnel.gateway.start() // デーモンを別プロセスとして spawn
355
+ funnel.gateway.getStatus() // { running, pid, port }
361
356
 
362
- await funnel.listeners.list() // talks to the running daemon over HTTP
363
357
  await funnel.listeners.start("inbox", "my-slack")
364
358
  await funnel.listeners.restart("inbox", "my-slack")
365
359
 
366
- await funnel.claude.launch({ channel: "inbox" })
367
- funnel.mcp.install("/path/to/repo") // writes .mcp.json
360
+ await funnel.claude.launch({ channel: "inbox" }) // claude を起動(.mcp.json も自動で書く)
368
361
  ```
369
362
 
370
- Run the gateway in-process (no daemon spawn — useful for tests or embedding):
363
+ デーモンを spawn せず、gateway をインプロセスで動かすこともできる(テストや埋め込み向け)。`onEvent` で全 broadcast イベントをインプロセスで観測できる。
371
364
 
372
365
  ```ts
373
366
  const server = funnel.gatewayServer({ port: 9742 })
374
367
  await server.start() // Bun.serve (HTTP + WS) + listener supervisor
375
368
  const unsubscribe = server.onEvent(({ content, meta }) => {
376
- console.log(meta?.connector, content) // in-process observer for every broadcast event
369
+ console.log(meta?.connector, content)
377
370
  })
378
371
  await server.stop()
379
372
  unsubscribe()
380
373
  ```
381
374
 
382
- Persistence and replay live behind the `FunnelEventLog` port. The default is a `SqliteFunnelEventLog` (durable across daemon restarts: it seeds the broadcaster's offset and serves reconnect replay). Inject `MemoryFunnelEventLog` or any `FunnelEventLog` — to swap or disable durable replay; `onEvent` is a separate, write-only observation hook and does not replace it:
375
+ 永続化と再生は `FunnelEventLog` port の裏にある。デフォルトは `SqliteFunnelEventLog`(デーモン再起動を跨いで durable。reconnect 時の再生を提供する)。`gatewayServer({ eventLog })` `MemoryFunnelEventLog` を渡せば durable な再生を差し替え・無効化できる。`onEvent` は書き込み専用の観測フックで、再生(読み戻し)は EventLog の責務。
383
376
 
384
- ```ts
385
- import { MemoryFunnelEventLog } from "@interactive-inc/claude-funnel"
377
+ ### テスト用のサンドボックス
386
378
 
387
- const server = funnel.gatewayServer({ port: 9742, eventLog: new MemoryFunnelEventLog() })
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:
379
+ `Funnel.inMemory()` は全 IO 境界(ディスク / プロセス / clock / UUID)を Memory 実装で配線済みの Funnel を返す。`props` の任意の部分集合で個々の seam を上書きできるので、実 FS や spawn に触れずにテストを書ける。
395
380
 
396
381
  ```ts
397
- import { Funnel } from "@interactive-inc/claude-funnel"
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
382
+ const funnel = Funnel.inMemory() // 実ディスク / プロセス / clock / UUID に触れない
383
+ funnel.channels.add({ name: "inbox" }) // インメモリ store を変更する
401
384
  ```
402
385
 
403
- The longhand form (for fine-grained control) is still available:
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.
386
+ `fnl` を支える Hono アプリ(`createCliApp` / `toRequest`)や、各コネクタの Zod スキーマ(`connectorConfigSchema`)も export している。詳細は型定義を参照。
463
387
 
464
388
  ## Claude Code skill
465
389
 
466
- This repo ships a Claude Code skill at `.claude/skills/funnel/SKILL.md`. It briefs Claude on the architecture and command groups, and tells it to defer flag-level details to `funnel <command> --help`.
390
+ このリポジトリは `.claude/skills/funnel/SKILL.md` Claude Code skill を同梱している。アーキテクチャとコマンド群を Claude に説明し、フラグレベルの詳細は `funnel <command> --help` に委ねるよう指示する。
467
391
 
468
- Project-scoped (auto): if you run `claude` inside this repo, the skill is picked up automatically — no install step.
392
+ プロジェクトスコープ(自動): このリポジトリ内で `claude` を実行すると skill が自動で読まれる。インストール手順は不要。
469
393
 
470
- Global (use the skill in any project): Claude Code does not currently provide a CLI to install skills from a remote URL, so copy the file into your personal skills directory:
394
+ グローバル(任意のプロジェクトで使う): Claude Code はリモート URL から skill をインストールする CLI を今のところ提供していないので、ファイルを個人の skills ディレクトリへコピーする。
471
395
 
472
396
  ```bash
473
- # from a clone of this repo
397
+ # このリポジトリの clone から
474
398
  mkdir -p ~/.claude/skills/funnel
475
399
  cp .claude/skills/funnel/SKILL.md ~/.claude/skills/funnel/
476
400
  ```
477
401
 
478
- Or fetch it directly without cloning:
402
+ clone せず直接取得することもできる。
479
403
 
480
404
  ```bash
481
405
  mkdir -p ~/.claude/skills/funnel
@@ -483,31 +407,31 @@ curl -fsSL https://raw.githubusercontent.com/interactive-inc/open-claude-funnel/
483
407
  -o ~/.claude/skills/funnel/SKILL.md
484
408
  ```
485
409
 
486
- After this, Claude Code will load the skill in any session.
410
+ これで Claude Code がどのセッションでも skill を読む。
487
411
 
488
- ## Development
412
+ ## 開発
489
413
 
490
414
  ```bash
491
415
  git clone https://github.com/interactive-inc/open-claude-funnel.git
492
416
  cd open-claude-funnel
493
- bun install # install deps (no auto-build)
494
- make build # produce dist/ — run this once after install
495
- bun link # symlinks fnl / funnel → dist/bin.js
496
- make build # rebuild library + CLI after editing
497
- make build-lib # library only (vp pack)
498
- make build-bin # CLI / daemon only (bun build --minify)
499
- make clean # remove dist/
500
- bun test # run tests
501
- bunx tsc -b # type check
502
- bun lib/bin.ts ... # run the cli from source (no build) for fast iteration
417
+ bun install # 依存インストール(自動ビルドなし)
418
+ make build # dist/ を生成 install 後に一度実行
419
+ bun link # fnl / funnel → dist/bin.js を symlink
420
+ make build # 編集後にライブラリ + CLI を再ビルド
421
+ make build-lib # ライブラリのみ(vp pack
422
+ make build-bin # CLI / daemon のみ(bun build --minify
423
+ make clean # dist/ を削除
424
+ bun test # テスト実行
425
+ bunx tsc -b # 型チェック
426
+ bun lib/bin.ts ... # ソースから CLI を実行(ビルド不要、高速イテレーション)
503
427
  ```
504
428
 
505
- ## Links
429
+ ## リンク
506
430
 
507
431
  - [GitHub](https://github.com/interactive-inc/open-claude-funnel)
508
432
  - [Issues](https://github.com/interactive-inc/open-claude-funnel/issues)
509
- - Coding rules and design principles: [CLAUDE.md](https://github.com/interactive-inc/open-claude-funnel/blob/main/CLAUDE.md)
433
+ - コーディング規約と設計原則: [CLAUDE.md](https://github.com/interactive-inc/open-claude-funnel/blob/main/CLAUDE.md)
510
434
 
511
- ## License
435
+ ## ライセンス
512
436
 
513
437
  MIT © Interactive Inc.