lark-remote 0.1.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/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.en.md +162 -0
- package/README.md +161 -0
- package/dist/binder.d.ts +57 -0
- package/dist/binder.js +105 -0
- package/dist/bridge/index.d.ts +338 -0
- package/dist/bridge/index.js +1281 -0
- package/dist/bridge/queue-manager.d.ts +277 -0
- package/dist/bridge/queue-manager.js +729 -0
- package/dist/card/bash-card-session.d.ts +78 -0
- package/dist/card/bash-card-session.js +174 -0
- package/dist/card/bash-renderer.d.ts +24 -0
- package/dist/card/bash-renderer.js +187 -0
- package/dist/card/card-budget.d.ts +31 -0
- package/dist/card/card-budget.js +280 -0
- package/dist/card/card-session.d.ts +83 -0
- package/dist/card/card-session.js +185 -0
- package/dist/card/card-shared.d.ts +28 -0
- package/dist/card/card-shared.js +99 -0
- package/dist/card/collapsible.d.ts +26 -0
- package/dist/card/collapsible.js +87 -0
- package/dist/card/run-card-session.d.ts +78 -0
- package/dist/card/run-card-session.js +165 -0
- package/dist/card/run-renderer.d.ts +66 -0
- package/dist/card/run-renderer.js +841 -0
- package/dist/card/run-state.d.ts +103 -0
- package/dist/card/run-state.js +313 -0
- package/dist/card/text-truncate.d.ts +56 -0
- package/dist/card/text-truncate.js +184 -0
- package/dist/card/time.d.ts +1 -0
- package/dist/card/time.js +14 -0
- package/dist/card/tool-render.d.ts +25 -0
- package/dist/card/tool-render.js +176 -0
- package/dist/cli.d.ts +28 -0
- package/dist/cli.js +89 -0
- package/dist/common/guards.d.ts +37 -0
- package/dist/common/guards.js +65 -0
- package/dist/config/codex-config.d.ts +113 -0
- package/dist/config/codex-config.js +428 -0
- package/dist/config/dir.d.ts +17 -0
- package/dist/config/dir.js +81 -0
- package/dist/config/index.d.ts +219 -0
- package/dist/config/index.js +388 -0
- package/dist/config/kimi-config.d.ts +33 -0
- package/dist/config/kimi-config.js +111 -0
- package/dist/config/opencode-config.d.ts +20 -0
- package/dist/config/opencode-config.js +124 -0
- package/dist/config/pi-config.d.ts +49 -0
- package/dist/config/pi-config.js +151 -0
- package/dist/config/wizard.d.ts +12 -0
- package/dist/config/wizard.js +84 -0
- package/dist/connector/file-limits.d.ts +7 -0
- package/dist/connector/file-limits.js +8 -0
- package/dist/connector/index.d.ts +86 -0
- package/dist/connector/index.js +340 -0
- package/dist/error-classification.d.ts +15 -0
- package/dist/error-classification.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +580 -0
- package/dist/instance-lock.d.ts +30 -0
- package/dist/instance-lock.js +184 -0
- package/dist/logger/index.d.ts +57 -0
- package/dist/logger/index.js +178 -0
- package/dist/order/index.d.ts +27 -0
- package/dist/order/index.js +90 -0
- package/dist/persistence/atomic-write.d.ts +10 -0
- package/dist/persistence/atomic-write.js +65 -0
- package/dist/persistence/load-json-file.d.ts +9 -0
- package/dist/persistence/load-json-file.js +23 -0
- package/dist/restart.d.ts +24 -0
- package/dist/restart.js +73 -0
- package/dist/router/card-helpers.d.ts +28 -0
- package/dist/router/card-helpers.js +65 -0
- package/dist/router/config/claude.d.ts +14 -0
- package/dist/router/config/claude.js +72 -0
- package/dist/router/config/codex.d.ts +22 -0
- package/dist/router/config/codex.js +100 -0
- package/dist/router/config/common/render.d.ts +16 -0
- package/dist/router/config/common/render.js +158 -0
- package/dist/router/config/index.d.ts +18 -0
- package/dist/router/config/index.js +38 -0
- package/dist/router/config/kimi.d.ts +21 -0
- package/dist/router/config/kimi.js +63 -0
- package/dist/router/config/opencode.d.ts +12 -0
- package/dist/router/config/opencode.js +44 -0
- package/dist/router/config/pi.d.ts +12 -0
- package/dist/router/config/pi.js +50 -0
- package/dist/router/config/types.d.ts +43 -0
- package/dist/router/config/types.js +8 -0
- package/dist/router/index.d.ts +318 -0
- package/dist/router/index.js +2715 -0
- package/dist/router/order-exec-dispatch.d.ts +30 -0
- package/dist/router/order-exec-dispatch.js +48 -0
- package/dist/router/utils.d.ts +44 -0
- package/dist/router/utils.js +116 -0
- package/dist/runner/bash/index.d.ts +4 -0
- package/dist/runner/bash/index.js +5 -0
- package/dist/runner/bash/runner.d.ts +55 -0
- package/dist/runner/bash/runner.js +213 -0
- package/dist/runner/claude/index.d.ts +4 -0
- package/dist/runner/claude/index.js +5 -0
- package/dist/runner/claude/runner.d.ts +29 -0
- package/dist/runner/claude/runner.js +83 -0
- package/dist/runner/codex/argv.d.ts +33 -0
- package/dist/runner/codex/argv.js +40 -0
- package/dist/runner/codex/index.d.ts +1 -0
- package/dist/runner/codex/index.js +2 -0
- package/dist/runner/codex/jsonl.d.ts +37 -0
- package/dist/runner/codex/jsonl.js +221 -0
- package/dist/runner/codex/runner.d.ts +65 -0
- package/dist/runner/codex/runner.js +124 -0
- package/dist/runner/common/exec-translator.d.ts +73 -0
- package/dist/runner/common/exec-translator.js +105 -0
- package/dist/runner/common/jsonl-stream.d.ts +39 -0
- package/dist/runner/common/jsonl-stream.js +203 -0
- package/dist/runner/common/process-stopper.d.ts +28 -0
- package/dist/runner/common/process-stopper.js +79 -0
- package/dist/runner/common/runner-utils.d.ts +21 -0
- package/dist/runner/common/runner-utils.js +65 -0
- package/dist/runner/common/spawn-heartbeat.d.ts +33 -0
- package/dist/runner/common/spawn-heartbeat.js +51 -0
- package/dist/runner/common/spawning-runner.d.ts +321 -0
- package/dist/runner/common/spawning-runner.js +634 -0
- package/dist/runner/common/usage.d.ts +31 -0
- package/dist/runner/common/usage.js +29 -0
- package/dist/runner/index.d.ts +19 -0
- package/dist/runner/index.js +24 -0
- package/dist/runner/kimi/index.d.ts +1 -0
- package/dist/runner/kimi/index.js +2 -0
- package/dist/runner/kimi/jsonl.d.ts +28 -0
- package/dist/runner/kimi/jsonl.js +143 -0
- package/dist/runner/kimi/runner.d.ts +50 -0
- package/dist/runner/kimi/runner.js +105 -0
- package/dist/runner/opencode/argv.d.ts +32 -0
- package/dist/runner/opencode/argv.js +35 -0
- package/dist/runner/opencode/index.d.ts +1 -0
- package/dist/runner/opencode/index.js +2 -0
- package/dist/runner/opencode/jsonl.d.ts +68 -0
- package/dist/runner/opencode/jsonl.js +295 -0
- package/dist/runner/opencode/runner.d.ts +63 -0
- package/dist/runner/opencode/runner.js +93 -0
- package/dist/runner/pi/index.d.ts +1 -0
- package/dist/runner/pi/index.js +2 -0
- package/dist/runner/pi/jsonl.d.ts +128 -0
- package/dist/runner/pi/jsonl.js +249 -0
- package/dist/runner/pi/runner.d.ts +37 -0
- package/dist/runner/pi/runner.js +76 -0
- package/dist/runner/registry.d.ts +54 -0
- package/dist/runner/registry.js +75 -0
- package/dist/runner/resolve-agent-choices.d.ts +7 -0
- package/dist/runner/resolve-agent-choices.js +76 -0
- package/dist/runner/sync-agent-choices.d.ts +6 -0
- package/dist/runner/sync-agent-choices.js +73 -0
- package/dist/runner/types.d.ts +250 -0
- package/dist/runner/types.js +8 -0
- package/dist/session/claude/index.d.ts +1 -0
- package/dist/session/claude/index.js +2 -0
- package/dist/session/claude/session-reader.d.ts +34 -0
- package/dist/session/claude/session-reader.js +46 -0
- package/dist/session/claude/sessions.d.ts +53 -0
- package/dist/session/claude/sessions.js +528 -0
- package/dist/session/codex/index.d.ts +1 -0
- package/dist/session/codex/index.js +2 -0
- package/dist/session/codex/rollout-reader.d.ts +88 -0
- package/dist/session/codex/rollout-reader.js +473 -0
- package/dist/session/codex/sessions.d.ts +21 -0
- package/dist/session/codex/sessions.js +40 -0
- package/dist/session/common/catch-up.d.ts +12 -0
- package/dist/session/common/catch-up.js +31 -0
- package/dist/session/common/constants.d.ts +11 -0
- package/dist/session/common/constants.js +12 -0
- package/dist/session/common/content-blocks.d.ts +37 -0
- package/dist/session/common/content-blocks.js +68 -0
- package/dist/session/common/jsonl.d.ts +77 -0
- package/dist/session/common/jsonl.js +351 -0
- package/dist/session/common/usage-accumulator.d.ts +58 -0
- package/dist/session/common/usage-accumulator.js +72 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +11 -0
- package/dist/session/kimi/index.d.ts +1 -0
- package/dist/session/kimi/index.js +2 -0
- package/dist/session/kimi/sessions.d.ts +81 -0
- package/dist/session/kimi/sessions.js +527 -0
- package/dist/session/opencode/index.d.ts +1 -0
- package/dist/session/opencode/index.js +2 -0
- package/dist/session/opencode/sessions.d.ts +81 -0
- package/dist/session/opencode/sessions.js +388 -0
- package/dist/session/pi/index.d.ts +1 -0
- package/dist/session/pi/index.js +2 -0
- package/dist/session/pi/sessions.d.ts +27 -0
- package/dist/session/pi/sessions.js +399 -0
- package/dist/session/registry.d.ts +12 -0
- package/dist/session/registry.js +20 -0
- package/dist/session/session-store.d.ts +86 -0
- package/dist/session/session-store.js +450 -0
- package/dist/startup-contact.d.ts +18 -0
- package/dist/startup-contact.js +51 -0
- package/dist/workspace/index.d.ts +19 -0
- package/dist/workspace/index.js +53 -0
- package/package.json +93 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-08-06
|
|
9
|
+
|
|
10
|
+
Initial release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Feishu private-chat ↔ local coding agent bridge (Claude Code / Codex / opencode / pi / Kimi)
|
|
15
|
+
- CardKit 2.0 single-card streaming: one card per run, updated in place via `im.v1.message.patch`
|
|
16
|
+
- Run card: thinking, text, tool summaries; terminal states (done/error/interrupted/idle_timeout/finalizing)
|
|
17
|
+
- Bash card: `!<cmd>` executes bash with streaming card output (bypasses serial queue)
|
|
18
|
+
- Serial work queue + stop control lane for concurrency safety
|
|
19
|
+
- 5 agent runners with JSONL/NDJSON parsing, lifecycle management, and orphan process handling
|
|
20
|
+
- 5 session readers with unified contract (`listSessions`/`readSessionContent`)
|
|
21
|
+
- `/config` interactive card (agent-aware): switch agent, model, reasoning effort, idle watchdog
|
|
22
|
+
- `/order` global prompt storage with `order.exec` dispatch
|
|
23
|
+
- `/resume` paginated session list with per-agent filtering
|
|
24
|
+
- `/active` in-memory dashboard of running sessions
|
|
25
|
+
- `/cd`, `/ls` (paginated file browser), `/ws` (workspace aliases)
|
|
26
|
+
- `/restart` in-place self-restart with process handoff
|
|
27
|
+
- `/stop` immediate SIGKILL; stop button on run cards
|
|
28
|
+
- QR-code scan-to-create wizard for first-time Feishu app setup
|
|
29
|
+
- Single-instance lock per config directory
|
|
30
|
+
- Startup notification to most recent private chat
|
|
31
|
+
- Session persistence across bridge restarts (`last-session.json`)
|
|
32
|
+
- Agent switch session restore with `previousSessions`/`arrivalSessions` dual-field design
|
|
33
|
+
- Idle watchdog with configurable timeout
|
|
34
|
+
- Run card progressive degradation under 28KB budget (text > thinking > tools)
|
|
35
|
+
- 28KB+ budget enforcement using `Buffer.byteLength` (UTF-8)
|
|
36
|
+
- Dedup TTL workaround for SDK `safety.dedup` toggle button issue
|
|
37
|
+
- `classifyRejection` for SDK throttle/detach rejection (recoverable 4xx → log only)
|
|
38
|
+
- `SpawnHeartbeat` for spawn-stage stall detection (30s, log-only)
|
|
39
|
+
- Atomic file writes (tmp + rename, EXDEV fallback)
|
|
40
|
+
- Daily log rotation (file-only, no stdout)
|
|
41
|
+
- Node.js 20+ compatible (zero Bun runtime APIs in src/)
|
|
42
|
+
- Bilingual documentation (Chinese + English)
|
|
43
|
+
- MIT License
|
|
44
|
+
|
|
45
|
+
### Security
|
|
46
|
+
|
|
47
|
+
- Full-permission agent execution (hardcoded `bypassPermissions`/`approval_policy=never`)
|
|
48
|
+
- Designed for single-user p2p private chat only — never add bot to group chats
|
|
49
|
+
- Credentials stored locally in `config.yaml`, never committed to repository
|
|
50
|
+
- Live Feishu API tests gated behind `FEISHU_LIVE_TEST=1` (external contributors can run full test suite without credentials)
|
|
51
|
+
|
|
52
|
+
[0.1.0]: https://github.com/hhr-code/lark-remote/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hhr-code
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# lark-remote
|
|
2
|
+
|
|
3
|
+
English | [简体中文](README.md)
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.npmjs.com/package/lark-remote)
|
|
7
|
+
[](https://github.com/hhr-code/lark-remote/actions/workflows/ci.yml)
|
|
8
|
+
|
|
9
|
+
A bridge between Feishu (Lark) private chat and local coding agents. Talk to Claude Code (or Codex / opencode / pi / Kimi) from Feishu — the agent reads/writes files and runs commands in a local directory you pick, with execution streamed live into a single CardKit 2.0 card.
|
|
10
|
+
|
|
11
|
+
Designed for a single-user, peer-to-peer private chat. The agent's system prompt is never modified.
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
> **New here? Start with [docs/en/getting-started.md](docs/en/getting-started.md).**
|
|
16
|
+
|
|
17
|
+
## ⚠️ Security Warning (read first)
|
|
18
|
+
|
|
19
|
+
This tool turns Feishu messages directly into local agent executions, and agents run with **full permissions** by default (Claude `bypassPermissions`, Codex `approval_policy=never` + full access) — effectively handing a shell on your machine to whoever can chat with the bot. You MUST:
|
|
20
|
+
|
|
21
|
+
- **Use it only in your own private (p2p) chat.** Never add the bot to any group chat; never let anyone else talk to it.
|
|
22
|
+
- Restrict the app's visibility to **yourself only** in the Feishu Open Platform.
|
|
23
|
+
- Don't run it on a machine holding data you can't afford to lose; prefer a dedicated user / machine / container.
|
|
24
|
+
- Keep credentials such as `appSecret` in your local `config.yaml` only — **never commit them anywhere**.
|
|
25
|
+
|
|
26
|
+
## Prerequisites
|
|
27
|
+
|
|
28
|
+
- OS: macOS / Linux. **Windows is not supported yet** (relies on POSIX behaviors such as Unix signals, bash, and file locks)
|
|
29
|
+
- Node.js 20+ ([Bun](https://bun.sh/) for development)
|
|
30
|
+
- A Feishu custom app (either way): scan-to-create (a QR code pops up in the terminal on first launch; scan it with the Feishu app to create the app and write credentials automatically); or create one manually in the Open Platform — enable bot capability, subscribe to `im.message.receive_v1` and `card.action.trigger` via **long connection** (WebSocket, no public address needed), with at least the `im:message` scope.
|
|
31
|
+
- Claude Code CLI installed locally and logged in once in a terminal (`claude` → browser OAuth). Same idea for other agents (codex / opencode / pi / kimi): install the corresponding CLI first.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Option 1: npm / npx (no clone needed):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx lark-remote # run directly
|
|
39
|
+
# or install globally
|
|
40
|
+
npm install -g lark-remote
|
|
41
|
+
lark-remote
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Option 2: from source:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/hhr-code/lark-remote.git
|
|
48
|
+
cd lark-remote
|
|
49
|
+
bun install
|
|
50
|
+
bun run build
|
|
51
|
+
bun install -g "$(pwd)" # install globally as the lark-remote command (bun parses `.` as an empty package name; use an absolute path)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Configuration
|
|
55
|
+
|
|
56
|
+
On first launch, if no Feishu credentials are found: an interactive terminal enters the scan-to-create wizard (a QR code is printed; scan it with the Feishu app to create the app, write credentials, and continue startup); non-interactive environments generate a template at `~/.lark-remote/config.yaml` and exit — fill in your credentials and restart. See [`docs/en/usage.md`](docs/en/usage.md) for all fields. Key options:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
feishu:
|
|
60
|
+
appId: cli_xxx
|
|
61
|
+
appSecret: xxx
|
|
62
|
+
|
|
63
|
+
# default agent: claude | codex | opencode | pi | kimi (default: claude)
|
|
64
|
+
defaultAgent: claude
|
|
65
|
+
|
|
66
|
+
claude:
|
|
67
|
+
binary: claude
|
|
68
|
+
model: claude-opus-4-8
|
|
69
|
+
effort: medium # low | medium | high | xhigh | max
|
|
70
|
+
# permissionMode is hardcoded to bypassPermissions (inside the runner)
|
|
71
|
+
stopGraceMs: 5000
|
|
72
|
+
|
|
73
|
+
output:
|
|
74
|
+
showThinking: true
|
|
75
|
+
showToolUse: true
|
|
76
|
+
showToolResult: true
|
|
77
|
+
|
|
78
|
+
logging:
|
|
79
|
+
level: info # debug | info | warn | error
|
|
80
|
+
|
|
81
|
+
idle:
|
|
82
|
+
watchdogMinutes: 15 # 0 disables the idle watchdog
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Override the config directory with `--config-dir <path>`; override the Claude settings path with `--settings <path>` or the `CLAUDE_SETTINGS_PATH` environment variable.
|
|
86
|
+
|
|
87
|
+
## Running
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
lark-remote # after global install / via npx
|
|
91
|
+
bun run dev # development (run TS directly with bun)
|
|
92
|
+
bun run build && node dist/index.js # run the compiled output
|
|
93
|
+
|
|
94
|
+
# CLI flags
|
|
95
|
+
lark-remote --config-dir ~/.lark-remote-test # custom config dir (multiple instances on one machine)
|
|
96
|
+
lark-remote --settings ~/.claude/settings.json # specify Claude settings file
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The bridge prints nothing to the terminal after startup; logs go to `~/.lark-remote/logs/YYYY-MM-DD/lark-remote-<pid>.log` (rotated daily, one subdirectory per day). Only one `lark-remote` instance is allowed per `configDir` — a duplicate start fails with the existing pid. After connecting to Feishu, a startup notification (with start time and pid) is sent to the most recent private chat.
|
|
100
|
+
|
|
101
|
+
For automatic process restart, set up your own supervisor such as systemd, launchd, or pm2.
|
|
102
|
+
|
|
103
|
+
Each agent run creates one CardKit 2.0 card that updates in place with thinking, body text, and tool summaries in real time.
|
|
104
|
+
Timestamps are read from the JSONL and shown in local time as `YYYY-MM-DD HH:mm` for thinking, body, tool call/result, and session history events.
|
|
105
|
+
The card clearly shows completion, error, interruption, or idle timeout at the end; the in-card "⏹ Stop" button is equivalent to `/stop`.
|
|
106
|
+
Cards use a lossy rolling summary — very long histories are not kept in full.
|
|
107
|
+
Your original message also gets an emoji reaction: `Typing` while the run is in progress, then the terminal-state emoji at the end — `Done` (finished), `ERROR` (failed), `Alarm` (idle timeout), or `SHHH` (you stopped it with `/stop`). `!` bash commands always end with `Done`.
|
|
108
|
+
|
|
109
|
+
## Commands
|
|
110
|
+
|
|
111
|
+
| Command | Alias | Behavior |
|
|
112
|
+
|---------|-------|----------|
|
|
113
|
+
| `/help` | `/h` | Command list |
|
|
114
|
+
| `/cd <path>` | - | Switch agent working directory (`~`, absolute/relative; clears the session) |
|
|
115
|
+
| `/ls [dir]` | - | Directory/file card; click a directory to switch, click a file ≤30MB to send it to Feishu; paginates beyond 30 entries |
|
|
116
|
+
| `/ws save\|use\|remove` | - | Named directory aliases (`/ws` lists by default) |
|
|
117
|
+
| `/resume [agent] [N\|id]` | `/r` | List/switch agent sessions for the current directory (card) |
|
|
118
|
+
| `/active` | - | List all running sessions |
|
|
119
|
+
| `/new` | - | Clear the current session (keeps cwd) |
|
|
120
|
+
| `/status` | `/s` | Current directory, session, model, process status |
|
|
121
|
+
| `/stop` | `/t` | Kill the current agent process (SIGKILL) |
|
|
122
|
+
| `/ps` | - | Whether a process is running |
|
|
123
|
+
| `/reconnect` | - | Reconnect to Feishu |
|
|
124
|
+
| `/restart` | - | In-place bridge self-restart (new process takes over with the same config) |
|
|
125
|
+
| `/config get\|set` | `/c` | View/change runtime config (agent-aware card) |
|
|
126
|
+
| `/order save\|list` | `/o` | Save or list frequently used prompts |
|
|
127
|
+
| `!<cmd>` | - | Run a bash command with streaming card output (bypasses the serial queue) |
|
|
128
|
+
| `/exit` | `/e` | Exit the bridge |
|
|
129
|
+
|
|
130
|
+
Any message not starting with `/` is forwarded to the current default agent.
|
|
131
|
+
|
|
132
|
+
## Testing
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
bun run test # vitest (unit/integration tests, all run offline)
|
|
136
|
+
bun run typecheck # tsc --noEmit
|
|
137
|
+
bun run lint # eslint
|
|
138
|
+
|
|
139
|
+
# Live Feishu API integration tests (skipped by default; need valid
|
|
140
|
+
# credentials under ~/.lark-remote-test)
|
|
141
|
+
FEISHU_LIVE_TEST=1 bun run test tests/feishu-card-form-error.test.ts
|
|
142
|
+
FEISHU_LIVE_TEST=1 bun run test tests/feishu-reaction-emoji-live.test.ts
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Documentation
|
|
146
|
+
|
|
147
|
+
Full docs under [`docs/`](docs/):
|
|
148
|
+
|
|
149
|
+
- Getting started: [`docs/en/getting-started.md`](docs/en/getting-started.md)
|
|
150
|
+
- Usage guide (install, commands, workflows): [`docs/en/usage.md`](docs/en/usage.md)
|
|
151
|
+
- Overall design, JSONL events, known pitfalls: [`docs/en/architecture/design.md`](docs/en/architecture/design.md)
|
|
152
|
+
- Single-card streaming architecture and Feishu acceptance: [`docs/en/architecture/streaming-card.md`](docs/en/architecture/streaming-card.md)
|
|
153
|
+
- Template for adding a new agent: [`docs/en/guides/add-new-agent.md`](docs/en/guides/add-new-agent.md)
|
|
154
|
+
- Codex config card guide: [`docs/en/guides/codex-config.md`](docs/en/guides/codex-config.md)
|
|
155
|
+
- Feishu CardKit 2.0 component reference: [Feishu Open Platform official docs](https://open.feishu.cn/document/feishu-cards/card-json-v2-components/component-json-v2-overview)
|
|
156
|
+
- AI collaboration rules and red lines: [`CLAUDE.md`](CLAUDE.md)
|
|
157
|
+
|
|
158
|
+
> This package is a CLI tool only — no programmatic API is exposed.
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
[MIT](LICENSE) © hhr-code
|
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# lark-remote
|
|
2
|
+
|
|
3
|
+
[English](README.en.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.npmjs.com/package/lark-remote)
|
|
7
|
+
[](https://github.com/hhr-code/lark-remote/actions/workflows/ci.yml)
|
|
8
|
+
|
|
9
|
+
飞书私聊 ↔ 本地 Coding Agent 桥接。在飞书里和 Claude Code(或 Codex / opencode / pi / Kimi)对话,agent 在你指定的本地目录里读写文件、跑命令,执行过程以 CardKit 2.0 卡片**单卡实时流式**呈现。
|
|
10
|
+
|
|
11
|
+
单用户、p2p 私聊场景设计,不修改 agent 的 system prompt。
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
> **第一次接触本项目?先看 [docs/zh/getting-started.md](docs/zh/getting-started.md)。**
|
|
16
|
+
|
|
17
|
+
## ⚠️ 安全警告(必读)
|
|
18
|
+
|
|
19
|
+
本工具会把飞书消息直接转为本地 agent 执行,agent 默认以**完全权限**运行(Claude `bypassPermissions`、Codex `approval_policy=never` + full access),等价于把一台机器的 shell 交给聊天对端。请务必:
|
|
20
|
+
|
|
21
|
+
- **仅限你自己的私聊(p2p)使用**。不要把机器人拉进任何群聊,不要让任何其他人能与它对话。
|
|
22
|
+
- 在飞书开放平台把应用可见范围限制到**只有你自己**。
|
|
23
|
+
- 运行本工具的机器上不要存放与损失承受力不符的数据;建议在独立用户/机器/容器里运行。
|
|
24
|
+
- `appSecret` 等凭据只在本地 `config.yaml`,**不要提交到任何仓库**。
|
|
25
|
+
|
|
26
|
+
## 前置条件
|
|
27
|
+
|
|
28
|
+
- 操作系统:macOS / Linux。**暂不支持 Windows**(依赖 Unix 信号、bash、文件锁等 POSIX 行为)
|
|
29
|
+
- Node.js 20+(开发时用 [Bun](https://bun.sh/))
|
|
30
|
+
- 飞书自建应用(二选一):扫码创建(首次启动终端弹二维码,飞书 App 扫码即自动创建并写入凭据);或手动在开放平台建应用——开启机器人能力,订阅 `im.message.receive_v1` 和 `card.action.trigger`,订阅方式选「长连接」(WebSocket,无需公网地址),权限至少 `im:message`。
|
|
31
|
+
- Claude Code CLI:本地安装并在终端完成一次登录(`claude` → 浏览器 OAuth)。使用其他 agent(codex / opencode / pi / kimi)同理,先装好对应 CLI。
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
方式一:npm / npx(无需 clone):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx lark-remote # 直接运行
|
|
39
|
+
# 或全局安装
|
|
40
|
+
npm install -g lark-remote
|
|
41
|
+
lark-remote
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
方式二:从源码安装:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/hhr-code/lark-remote.git
|
|
48
|
+
cd lark-remote
|
|
49
|
+
bun install
|
|
50
|
+
bun run build
|
|
51
|
+
bun install -g "$(pwd)" # 全局安装为 lark-remote 命令(相对路径 `.` 会触发 bun unsafe name bug,须绝对路径)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 配置
|
|
55
|
+
|
|
56
|
+
首次启动若检测不到飞书凭据:交互式终端会进入扫码创建向导(终端打印二维码,飞书 App 扫码即创建应用并写入凭据,随后继续启动);非交互环境则在 `~/.lark-remote/config.yaml` 生成模板并退出,填写凭据后重启。完整字段见 [`docs/zh/usage.md`](docs/zh/usage.md),关键项:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
feishu:
|
|
60
|
+
appId: cli_xxx
|
|
61
|
+
appSecret: xxx
|
|
62
|
+
|
|
63
|
+
# 默认 agent:claude | codex | opencode | pi | kimi,默认 claude
|
|
64
|
+
defaultAgent: claude
|
|
65
|
+
|
|
66
|
+
claude:
|
|
67
|
+
binary: claude
|
|
68
|
+
model: claude-opus-4-8
|
|
69
|
+
effort: medium # low | medium | high | xhigh | max
|
|
70
|
+
# permissionMode 硬编码为 bypassPermissions(runner 内部)
|
|
71
|
+
stopGraceMs: 5000
|
|
72
|
+
|
|
73
|
+
output:
|
|
74
|
+
showThinking: true
|
|
75
|
+
showToolUse: true
|
|
76
|
+
showToolResult: true
|
|
77
|
+
|
|
78
|
+
logging:
|
|
79
|
+
level: info # debug | info | warn | error
|
|
80
|
+
|
|
81
|
+
idle:
|
|
82
|
+
watchdogMinutes: 15 # 0 关闭空闲看门狗
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
配置目录可用 `--config-dir <path>` 覆盖;Claude settings 路径可用 `--settings <path>` 或 `CLAUDE_SETTINGS_PATH` 环境变量覆盖。
|
|
86
|
+
|
|
87
|
+
## 运行
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
lark-remote # 全局安装 / npx 后直接用
|
|
91
|
+
bun run dev # 开发(bun 直跑 TS)
|
|
92
|
+
bun run build && node dist/index.js # 编译后运行
|
|
93
|
+
|
|
94
|
+
# CLI 参数
|
|
95
|
+
lark-remote --config-dir ~/.lark-remote-test # 自定义配置目录(同机多实例)
|
|
96
|
+
lark-remote --settings ~/.claude/settings.json # 指定 Claude 配置文件
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
bridge 启动后不在终端输出,运行日志写入 `~/.lark-remote/logs/YYYY-MM-DD/lark-remote-<pid>.log`(按日期轮转,每天一个子目录)。同一 `configDir` 只允许一个 `lark-remote` 实例,重复启动会直接报错并提示已有 pid。连接飞书成功后会向最近私聊用户发送启动通知,包含启动时间和进程号。
|
|
100
|
+
|
|
101
|
+
如需进程自动重启,可自行配置 systemd / launchd / pm2 等守护方案。
|
|
102
|
+
|
|
103
|
+
每次 agent 运行会创建一张 CardKit 2.0 卡片,并在原地实时更新 thinking、正文和工具摘要。
|
|
104
|
+
卡片会读取 JSONL 的 `timestamp`,以本地时间 `YYYY-MM-DD HH:mm` 显示 thinking、正文、tool call/result 和会话历史事件。
|
|
105
|
+
结束时卡片明确显示完成、出错、中断或空闲超时;运行中的「⏹ 终止」按钮与 `/stop` 等价。
|
|
106
|
+
卡片采用有损滚动摘要,超长历史不会完整保留。
|
|
107
|
+
你的原消息上会收到表情回应:处理中先打 `Typing`,结束时按终态补打 `Done`(完成)/ `ERROR`(出错)/ `Alarm`(空闲超时)/ `SHHH`(你主动 `/stop`);`!` bash 命令结束时固定打 `Done`。
|
|
108
|
+
|
|
109
|
+
## 命令
|
|
110
|
+
|
|
111
|
+
| 命令 | 别名 | 行为 |
|
|
112
|
+
|------|------|------|
|
|
113
|
+
| `/help` | `/h` | 命令列表 |
|
|
114
|
+
| `/cd <path>` | - | 切换 agent 工作目录(支持 `~`、绝对/相对路径,清空会话) |
|
|
115
|
+
| `/ls [dir]` | - | 弹出目录/文件卡片;点击目录切换,点击 30MB 内文件发送到飞书;条目 >30 时支持翻页 |
|
|
116
|
+
| `/ws save\|use\|remove` | - | 命名目录别名管理(`/ws` 默认列出) |
|
|
117
|
+
| `/resume [agent] [N\|id]` | `/r` | 列出/切换当前目录的 agent session(卡片) |
|
|
118
|
+
| `/active` | - | 列出所有正在进行中的 session |
|
|
119
|
+
| `/new` | - | 清空当前会话(保留工作目录) |
|
|
120
|
+
| `/status` | `/s` | 当前目录、session、模型、进程状态 |
|
|
121
|
+
| `/stop` | `/t` | 终止当前 agent 进程(SIGKILL) |
|
|
122
|
+
| `/ps` | - | 是否有进程在跑 |
|
|
123
|
+
| `/reconnect` | - | 重连飞书 |
|
|
124
|
+
| `/restart` | - | 原地自重启 bridge(新进程同 config 接管) |
|
|
125
|
+
| `/config get\|set` | `/c` | 查改运行时配置(agent-aware 卡片) |
|
|
126
|
+
| `/order save\|list` | `/o` | 保存或列出常用指令 |
|
|
127
|
+
| `!<cmd>` | - | 执行 bash 命令并流式输出到卡片(绕过串行队列) |
|
|
128
|
+
| `/exit` | `/e` | 退出 bridge |
|
|
129
|
+
|
|
130
|
+
直接发非 `/` 开头的消息即转发给当前默认 agent。
|
|
131
|
+
|
|
132
|
+
## 测试
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
bun run test # vitest(单元/集成测试,全部离线可跑)
|
|
136
|
+
bun run typecheck # tsc --noEmit
|
|
137
|
+
bun run lint # eslint
|
|
138
|
+
|
|
139
|
+
# 真实飞书 API 集成测试(默认跳过,需要 ~/.lark-remote-test 下的有效凭据)
|
|
140
|
+
FEISHU_LIVE_TEST=1 bun run test tests/feishu-card-form-error.test.ts
|
|
141
|
+
FEISHU_LIVE_TEST=1 bun run test tests/feishu-reaction-emoji-live.test.ts
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## 文档
|
|
145
|
+
|
|
146
|
+
完整文档目录 [`docs/`](docs/):
|
|
147
|
+
|
|
148
|
+
- 入门指南:[`docs/zh/getting-started.md`](docs/zh/getting-started.md)
|
|
149
|
+
- 使用指南(安装、命令、工作流示例):[`docs/zh/usage.md`](docs/zh/usage.md)
|
|
150
|
+
- 整体设计、JSONL 事件、已知坑点:[`docs/zh/architecture/design.md`](docs/zh/architecture/design.md)
|
|
151
|
+
- 单卡流式架构与飞书验收:[`docs/zh/architecture/streaming-card.md`](docs/zh/architecture/streaming-card.md)
|
|
152
|
+
- 新增 agent 接入模板:[`docs/zh/guides/add-new-agent.md`](docs/zh/guides/add-new-agent.md)
|
|
153
|
+
- Codex 配置卡片指南:[`docs/zh/guides/codex-config.md`](docs/zh/guides/codex-config.md)
|
|
154
|
+
- 飞书 CardKit 2.0 组件参考:[飞书开放平台官方文档](https://open.feishu.cn/document/feishu-cards/card-json-v2-components/component-json-v2-overview)
|
|
155
|
+
- AI 协作规则与红线:[`CLAUDE.md`](CLAUDE.md)
|
|
156
|
+
|
|
157
|
+
> 本包为纯 CLI 工具,不提供编程 API。
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
[MIT](LICENSE) © hhr-code
|
package/dist/binder.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { StartupContactStore } from './startup-contact.js';
|
|
2
|
+
/** 入站私聊消息的绑定/授权判定结果。 */
|
|
3
|
+
type BindDecision = {
|
|
4
|
+
kind: 'owner';
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'rejected';
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'bind_success';
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'pin_wrong';
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Owner 绑定器:首次私聊输入正确 PIN 完成"认领",此后仅该 openId 可用。
|
|
14
|
+
*
|
|
15
|
+
* 设计依据(第一性原理):bridge 进程持有用户本机完整权限,唯一合法主体是
|
|
16
|
+
* owner 本人。任何入站事件都必须认证到该单一主体。`chatType==='p2p'` 不是认证
|
|
17
|
+
* (任何能私聊机器人的用户都满足)。本类以"首次绑定 + openId 强校验"实现认证,
|
|
18
|
+
* 复用已有 startup-contact.json,不新增配置字段。
|
|
19
|
+
*
|
|
20
|
+
* 状态机:
|
|
21
|
+
* - 未绑定:生成 PIN(控制台展示),等待 owner 私聊输入正确 PIN -> 写入绑定。
|
|
22
|
+
* 输错 PIN 完全静默(不回复、不提醒);首次启动仅用户本机可见,无需严格防枚举。
|
|
23
|
+
* - 已绑定:仅 senderId === bound.userId 的消息放行;其余静默丢弃(计数 + debug)。
|
|
24
|
+
*
|
|
25
|
+
* cardAction 同样校验 operator.openId === bound.userId(未绑定也视为非 owner)。
|
|
26
|
+
*/
|
|
27
|
+
export declare class OwnerBinder {
|
|
28
|
+
private readonly store;
|
|
29
|
+
private pin;
|
|
30
|
+
private rejectedTotal;
|
|
31
|
+
constructor(store: StartupContactStore);
|
|
32
|
+
/** 仅未绑定时返回待输入的 PIN(控制台展示用)。 */
|
|
33
|
+
get pendingPin(): string | undefined;
|
|
34
|
+
isBound(): boolean;
|
|
35
|
+
/** 已绑定的 owner openId;未绑定时 undefined。 */
|
|
36
|
+
boundOpenId(): string | undefined;
|
|
37
|
+
/** 卡片操作者是否为已绑定的 owner(未绑定返回 false)。 */
|
|
38
|
+
isOwner(openId: string): boolean;
|
|
39
|
+
/** 累计被拒(非 owner)消息/卡片数,用于 DoS 可观测。 */
|
|
40
|
+
get rejectedCount(): number;
|
|
41
|
+
/**
|
|
42
|
+
* 对入站私聊消息做绑定/授权判定,含副作用:
|
|
43
|
+
* - `owner`:放行,调用方继续正常处理
|
|
44
|
+
* - `rejected`:已绑定但非 owner,静默丢弃(计数 + debug)
|
|
45
|
+
* - `bind_success`:未绑定且 PIN 正确,写入绑定并清除 PIN
|
|
46
|
+
* - `pin_wrong`:未绑定且 PIN 错误,静默丢弃(不回复、不提醒)
|
|
47
|
+
*/
|
|
48
|
+
classify(senderId: string, content: string, chatId: string): BindDecision;
|
|
49
|
+
/** 卡片操作被拒时计数(与消息路径共用计数器)。 */
|
|
50
|
+
recordRejectedCardAction(openId: string): void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 控制台 PIN 引导文案(首次未绑定时输出到 stderr;守护模式下被 watchdog
|
|
54
|
+
* 重定向到 daemon 日志)。绑定后此文案不再出现。
|
|
55
|
+
*/
|
|
56
|
+
export declare function formatPinGuidance(pin: string): string;
|
|
57
|
+
export {};
|
package/dist/binder.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { getLogger } from './logger/index.js';
|
|
3
|
+
/** 绑定 PIN 位数。4 位数字便于人工输入。 */
|
|
4
|
+
const PIN_DIGITS = 4;
|
|
5
|
+
/** 用 CSPRNG 生成固定长度、零填充的数字 PIN(首位可为 0)。 */
|
|
6
|
+
function generatePin() {
|
|
7
|
+
const n = crypto.randomInt(0, 10 ** PIN_DIGITS);
|
|
8
|
+
return String(n).padStart(PIN_DIGITS, '0');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Owner 绑定器:首次私聊输入正确 PIN 完成"认领",此后仅该 openId 可用。
|
|
12
|
+
*
|
|
13
|
+
* 设计依据(第一性原理):bridge 进程持有用户本机完整权限,唯一合法主体是
|
|
14
|
+
* owner 本人。任何入站事件都必须认证到该单一主体。`chatType==='p2p'` 不是认证
|
|
15
|
+
* (任何能私聊机器人的用户都满足)。本类以"首次绑定 + openId 强校验"实现认证,
|
|
16
|
+
* 复用已有 startup-contact.json,不新增配置字段。
|
|
17
|
+
*
|
|
18
|
+
* 状态机:
|
|
19
|
+
* - 未绑定:生成 PIN(控制台展示),等待 owner 私聊输入正确 PIN -> 写入绑定。
|
|
20
|
+
* 输错 PIN 完全静默(不回复、不提醒);首次启动仅用户本机可见,无需严格防枚举。
|
|
21
|
+
* - 已绑定:仅 senderId === bound.userId 的消息放行;其余静默丢弃(计数 + debug)。
|
|
22
|
+
*
|
|
23
|
+
* cardAction 同样校验 operator.openId === bound.userId(未绑定也视为非 owner)。
|
|
24
|
+
*/
|
|
25
|
+
export class OwnerBinder {
|
|
26
|
+
store;
|
|
27
|
+
pin;
|
|
28
|
+
rejectedTotal = 0;
|
|
29
|
+
constructor(store) {
|
|
30
|
+
this.store = store;
|
|
31
|
+
if (!store.getContact()) {
|
|
32
|
+
this.pin = generatePin();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** 仅未绑定时返回待输入的 PIN(控制台展示用)。 */
|
|
36
|
+
get pendingPin() {
|
|
37
|
+
return this.pin;
|
|
38
|
+
}
|
|
39
|
+
isBound() {
|
|
40
|
+
return this.store.getContact() !== undefined;
|
|
41
|
+
}
|
|
42
|
+
/** 已绑定的 owner openId;未绑定时 undefined。 */
|
|
43
|
+
boundOpenId() {
|
|
44
|
+
return this.store.getContact()?.userId;
|
|
45
|
+
}
|
|
46
|
+
/** 卡片操作者是否为已绑定的 owner(未绑定返回 false)。 */
|
|
47
|
+
isOwner(openId) {
|
|
48
|
+
const bound = this.store.getContact()?.userId;
|
|
49
|
+
return bound !== undefined && bound === openId;
|
|
50
|
+
}
|
|
51
|
+
/** 累计被拒(非 owner)消息/卡片数,用于 DoS 可观测。 */
|
|
52
|
+
get rejectedCount() {
|
|
53
|
+
return this.rejectedTotal;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 对入站私聊消息做绑定/授权判定,含副作用:
|
|
57
|
+
* - `owner`:放行,调用方继续正常处理
|
|
58
|
+
* - `rejected`:已绑定但非 owner,静默丢弃(计数 + debug)
|
|
59
|
+
* - `bind_success`:未绑定且 PIN 正确,写入绑定并清除 PIN
|
|
60
|
+
* - `pin_wrong`:未绑定且 PIN 错误,静默丢弃(不回复、不提醒)
|
|
61
|
+
*/
|
|
62
|
+
classify(senderId, content, chatId) {
|
|
63
|
+
const bound = this.store.getContact();
|
|
64
|
+
if (bound) {
|
|
65
|
+
if (senderId === bound.userId)
|
|
66
|
+
return { kind: 'owner' };
|
|
67
|
+
this.rejectedTotal++;
|
|
68
|
+
getLogger().debug(`[binder] rejected message from ${senderId} (total rejected=${this.rejectedCount})`);
|
|
69
|
+
return { kind: 'rejected' };
|
|
70
|
+
}
|
|
71
|
+
// 未绑定:要求 PIN(精确匹配 trim 后内容,避免部分匹配绕过)
|
|
72
|
+
if (this.pin !== undefined && content.trim() === this.pin) {
|
|
73
|
+
this.store.save({ chatId, userId: senderId });
|
|
74
|
+
this.pin = undefined;
|
|
75
|
+
getLogger().info(`[binder] owner bound: openId=${senderId} chatId=${chatId}`);
|
|
76
|
+
return { kind: 'bind_success' };
|
|
77
|
+
}
|
|
78
|
+
// 输错 PIN:完全静默(不回复、不提醒、不计数),仅内部 debug 日志
|
|
79
|
+
getLogger().debug('[binder] wrong pin (unbound, ignored)');
|
|
80
|
+
return { kind: 'pin_wrong' };
|
|
81
|
+
}
|
|
82
|
+
/** 卡片操作被拒时计数(与消息路径共用计数器)。 */
|
|
83
|
+
recordRejectedCardAction(openId) {
|
|
84
|
+
this.rejectedTotal++;
|
|
85
|
+
getLogger().debug(`[binder] rejected card action from ${openId} (total rejected=${this.rejectedCount})`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 控制台 PIN 引导文案(首次未绑定时输出到 stderr;守护模式下被 watchdog
|
|
90
|
+
* 重定向到 daemon 日志)。绑定后此文案不再出现。
|
|
91
|
+
*/
|
|
92
|
+
export function formatPinGuidance(pin) {
|
|
93
|
+
return [
|
|
94
|
+
'',
|
|
95
|
+
'🔒 首次绑定',
|
|
96
|
+
'请在飞书私聊本应用,发送以下 4 位数字完成绑定:',
|
|
97
|
+
'',
|
|
98
|
+
` ${pin}`,
|
|
99
|
+
'',
|
|
100
|
+
'绑定后仅该账号可使用本应用。',
|
|
101
|
+
'需更换账号:删除 <configDir>/startup-contact.json 后重启。',
|
|
102
|
+
'',
|
|
103
|
+
].join('\n');
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=binder.js.map
|