wezard 0.3.3

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.
Files changed (117) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.claude-plugin/plugin.json +14 -0
  3. package/CLAUDE.md +83 -0
  4. package/LICENSE +21 -0
  5. package/README.md +410 -0
  6. package/cli/weclaude.sh +236 -0
  7. package/commands/audit.md +7 -0
  8. package/commands/wrc.md +7 -0
  9. package/config.example.jsonc +102 -0
  10. package/dist/cli/audit.js +156 -0
  11. package/dist/cli/audit.js.map +1 -0
  12. package/dist/cli/init.js +311 -0
  13. package/dist/cli/init.js.map +1 -0
  14. package/dist/cli/sync.js +175 -0
  15. package/dist/cli/sync.js.map +1 -0
  16. package/dist/daemon/approval.js +1301 -0
  17. package/dist/daemon/approval.js.map +1 -0
  18. package/dist/daemon/ask.js +101 -0
  19. package/dist/daemon/ask.js.map +1 -0
  20. package/dist/daemon/audit.js +168 -0
  21. package/dist/daemon/audit.js.map +1 -0
  22. package/dist/daemon/cc-bridge.js +204 -0
  23. package/dist/daemon/cc-bridge.js.map +1 -0
  24. package/dist/daemon/cfg-sync.js +240 -0
  25. package/dist/daemon/cfg-sync.js.map +1 -0
  26. package/dist/daemon/claim.js +95 -0
  27. package/dist/daemon/claim.js.map +1 -0
  28. package/dist/daemon/detail.js +153 -0
  29. package/dist/daemon/detail.js.map +1 -0
  30. package/dist/daemon/graph.js +176 -0
  31. package/dist/daemon/graph.js.map +1 -0
  32. package/dist/daemon/http.js +83 -0
  33. package/dist/daemon/http.js.map +1 -0
  34. package/dist/daemon/inbound.js +919 -0
  35. package/dist/daemon/inbound.js.map +1 -0
  36. package/dist/daemon/index.js +569 -0
  37. package/dist/daemon/index.js.map +1 -0
  38. package/dist/daemon/last-response.js +59 -0
  39. package/dist/daemon/last-response.js.map +1 -0
  40. package/dist/daemon/mirror-bridge.js +3512 -0
  41. package/dist/daemon/mirror-bridge.js.map +1 -0
  42. package/dist/daemon/mirror-store.js +39 -0
  43. package/dist/daemon/mirror-store.js.map +1 -0
  44. package/dist/daemon/outbound.js +42 -0
  45. package/dist/daemon/outbound.js.map +1 -0
  46. package/dist/daemon/peers.js +146 -0
  47. package/dist/daemon/peers.js.map +1 -0
  48. package/dist/daemon/pending.js +90 -0
  49. package/dist/daemon/pending.js.map +1 -0
  50. package/dist/daemon/quota.js +164 -0
  51. package/dist/daemon/quota.js.map +1 -0
  52. package/dist/daemon/redact.js +27 -0
  53. package/dist/daemon/redact.js.map +1 -0
  54. package/dist/daemon/session-cache.js +149 -0
  55. package/dist/daemon/session-cache.js.map +1 -0
  56. package/dist/daemon/session-scan.js +273 -0
  57. package/dist/daemon/session-scan.js.map +1 -0
  58. package/dist/daemon/sessions.js +35 -0
  59. package/dist/daemon/sessions.js.map +1 -0
  60. package/dist/daemon/spawn-tmux.js +213 -0
  61. package/dist/daemon/spawn-tmux.js.map +1 -0
  62. package/dist/daemon/tips.js +25 -0
  63. package/dist/daemon/tips.js.map +1 -0
  64. package/dist/daemon/topics.js +197 -0
  65. package/dist/daemon/topics.js.map +1 -0
  66. package/dist/daemon/usage.js +358 -0
  67. package/dist/daemon/usage.js.map +1 -0
  68. package/dist/daemon/wedoc.js +210 -0
  69. package/dist/daemon/wedoc.js.map +1 -0
  70. package/dist/daemon/ws.js +67 -0
  71. package/dist/daemon/ws.js.map +1 -0
  72. package/dist/mcp/server.js +385 -0
  73. package/dist/mcp/server.js.map +1 -0
  74. package/dist/shared/ansi.js +134 -0
  75. package/dist/shared/ansi.js.map +1 -0
  76. package/dist/shared/chat-http.js +195 -0
  77. package/dist/shared/chat-http.js.map +1 -0
  78. package/dist/shared/chat-render.js +53 -0
  79. package/dist/shared/chat-render.js.map +1 -0
  80. package/dist/shared/chat-view.js +116 -0
  81. package/dist/shared/chat-view.js.map +1 -0
  82. package/dist/shared/cli-backends.js +304 -0
  83. package/dist/shared/cli-backends.js.map +1 -0
  84. package/dist/shared/config-writer.js +39 -0
  85. package/dist/shared/config-writer.js.map +1 -0
  86. package/dist/shared/config.js +278 -0
  87. package/dist/shared/config.js.map +1 -0
  88. package/dist/shared/detail-render.js +836 -0
  89. package/dist/shared/detail-render.js.map +1 -0
  90. package/dist/shared/detail-store.js +205 -0
  91. package/dist/shared/detail-store.js.map +1 -0
  92. package/dist/shared/highlight.js +154 -0
  93. package/dist/shared/highlight.js.map +1 -0
  94. package/dist/shared/lan-ip.js +35 -0
  95. package/dist/shared/lan-ip.js.map +1 -0
  96. package/dist/shared/log.js +18 -0
  97. package/dist/shared/log.js.map +1 -0
  98. package/dist/shared/md-chunk.js +99 -0
  99. package/dist/shared/md-chunk.js.map +1 -0
  100. package/dist/shared/paths.js +12 -0
  101. package/dist/shared/paths.js.map +1 -0
  102. package/dist/shared/session-label.js +67 -0
  103. package/dist/shared/session-label.js.map +1 -0
  104. package/dist/shared/web-assets.js +51 -0
  105. package/dist/shared/web-assets.js.map +1 -0
  106. package/dist/svr/index.js +246 -0
  107. package/dist/svr/index.js.map +1 -0
  108. package/hooks/hooks.json +16 -0
  109. package/hooks/pre-tool-use.sh +167 -0
  110. package/launchd/com.weclaude.daemon.plist.template +45 -0
  111. package/package.json +97 -0
  112. package/scripts/install.sh +73 -0
  113. package/scripts/postinstall.sh +12 -0
  114. package/scripts/uninstall.sh +44 -0
  115. package/systemd/weclaude.service.template +17 -0
  116. package/web/chat.css +171 -0
  117. package/web/chat.js +367 -0
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env bash
2
+ # weclaude CLI — talks to local weclaude daemon over HTTP and toggles launchd/systemd.
3
+ set -uo pipefail
4
+
5
+ DAEMON_BASE="${WECLAUDE_DAEMON_BASE:-http://127.0.0.1:17890}"
6
+ LABEL="com.weclaude.daemon"
7
+ HOME_DIR="$HOME"
8
+ OS="$(uname -s)"
9
+
10
+ # Resolve symlinks so REPO_ROOT points at the real package dir even when this
11
+ # script is invoked via the npm-installed bin symlink (e.g. ~/.npm-global/bin/weclaude).
12
+ # macOS ships BSD readlink without -f, so chase the chain by hand.
13
+ self="${BASH_SOURCE[0]}"
14
+ while [[ -L "$self" ]]; do
15
+ d="$(cd -P "$(dirname "$self")" && pwd)"
16
+ t="$(readlink "$self")"
17
+ [[ "$t" != /* ]] && t="$d/$t"
18
+ self="$t"
19
+ done
20
+ REPO_ROOT="$(cd -P "$(dirname "$self")/.." && pwd)"
21
+
22
+ cmd="${1:-status}"
23
+ shift || true
24
+
25
+ usage() {
26
+ cat <<'EOF'
27
+ weclaude <subcommand>
28
+ init interactive onboarding (write config, install daemon, claim default chat, run demo)
29
+ status daemon health + connection state
30
+ start load resident daemon (launchd/systemd)
31
+ stop unload resident daemon
32
+ restart stop + start
33
+ reload quick: shutdown via HTTP + kickstart
34
+ mirror [chat] attach current Claude session for mirror push (target overrides config)
35
+ mirror-status show current mirror attachment
36
+ send <chat> <text> proactive markdown message
37
+ pending list outstanding approval req_ids
38
+ svr [args...] run standalone detail relay (deploy on a host chat + cli both reach)
39
+ logs [-f] tail daemon log
40
+ config-path show resolved config path
41
+ sync write hooks/MCP/env into sync.targets settings.json
42
+ unsync remove our entries from sync.targets settings.json
43
+ audit [tag] token/cost breakdown for current Claude session (main + subagents)
44
+ uninstall full teardown: stop daemon + unsync + plugin uninstall + remove daemon (run before `npm uninstall -g`)
45
+ add `--purge` to also delete ~/.weclaude state
46
+ version print weclaude version
47
+ help
48
+ EOF
49
+ }
50
+
51
+ http_get() { curl -sS --max-time 5 "$DAEMON_BASE$1"; }
52
+ http_post() { curl -sS --max-time 5 -X POST -H 'content-type: application/json' -d "${2:-{\}}" "$DAEMON_BASE$1"; }
53
+
54
+ svc_load() {
55
+ case "$OS" in
56
+ Darwin) launchctl load -w "$HOME_DIR/Library/LaunchAgents/${LABEL}.plist" ;;
57
+ Linux) systemctl --user start weclaude.service ;;
58
+ esac
59
+ }
60
+ svc_unload() {
61
+ case "$OS" in
62
+ Darwin) launchctl unload "$HOME_DIR/Library/LaunchAgents/${LABEL}.plist" ;;
63
+ Linux) systemctl --user stop weclaude.service ;;
64
+ esac
65
+ }
66
+
67
+ case "$cmd" in
68
+ init)
69
+ NODE_BIN="$(command -v node)"
70
+ SCRIPT="$REPO_ROOT/dist/cli/init.js"
71
+ if [[ ! -f "$SCRIPT" ]]; then
72
+ # Dev repo: tsconfig.json present → build. npm install: tarball ships dist/,
73
+ # so a missing init.js means a broken install — fail clearly instead of
74
+ # invoking tsc against a non-existent tsconfig.
75
+ if [[ -f "$REPO_ROOT/tsconfig.json" ]]; then
76
+ echo "[weclaude init] building..."
77
+ (cd "$REPO_ROOT" && npm install --silent && npx tsc -p tsconfig.json) || { echo "build failed"; exit 1; }
78
+ else
79
+ echo "[weclaude init] missing $SCRIPT — try 'npm install -g weclaude' to reinstall" >&2
80
+ exit 1
81
+ fi
82
+ fi
83
+ exec "$NODE_BIN" "$SCRIPT" "$@"
84
+ ;;
85
+ status)
86
+ if ! out=$(http_get /status 2>/dev/null); then
87
+ echo "daemon: down ($DAEMON_BASE)"
88
+ exit 1
89
+ fi
90
+ echo "$out" | jq . 2>/dev/null || echo "$out"
91
+ ;;
92
+ start) svc_load && echo "daemon: started" ;;
93
+ stop) svc_unload && echo "daemon: stopped" ;;
94
+ restart) svc_unload || true; sleep 1; svc_load && echo "daemon: restarted" ;;
95
+ reload)
96
+ # KeepAlive.SuccessfulExit=false, so /shutdown alone won't respawn — kick it.
97
+ http_post /shutdown >/dev/null 2>&1 || true
98
+ case "$OS" in
99
+ Darwin) launchctl kickstart -k "gui/$(id -u)/${LABEL}" >/dev/null && echo "daemon: reloaded" ;;
100
+ Linux) systemctl --user restart weclaude.service && echo "daemon: reloaded" ;;
101
+ esac
102
+ ;;
103
+ send)
104
+ chat="${1:-}"; shift || true
105
+ text="${*:-}"
106
+ if [[ -z "$chat" || -z "$text" ]]; then
107
+ echo "usage: weclaude send <chat> <text>"; exit 1
108
+ fi
109
+ body=$(jq -nc --arg c "$chat" --arg t "$text" '{chat:$c,text:$t}')
110
+ http_post /message "$body"
111
+ ;;
112
+ pending) http_get /pending | jq . 2>/dev/null || http_get /pending ;;
113
+ svr)
114
+ NODE_BIN="$(command -v node)"
115
+ SCRIPT="$REPO_ROOT/dist/svr/index.js"
116
+ [[ -f "$SCRIPT" ]] || { echo "build first: npm run build"; exit 1; }
117
+ exec "$NODE_BIN" "$SCRIPT" "$@"
118
+ ;;
119
+ logs)
120
+ log_path=$(http_get /status 2>/dev/null | jq -r '.logFile // empty' 2>/dev/null || true)
121
+ log_path="${log_path:-$HOME_DIR/.weclaude/daemon.log}"
122
+ if [[ "${1:-}" == "-f" ]]; then tail -f "$log_path"; else tail -n 100 "$log_path"; fi
123
+ ;;
124
+ config-path) http_get /status | jq -r '.sourcePath // empty' ;;
125
+ uninstall)
126
+ # Order matters: stop the daemon FIRST so it can't rewrite settings/lock
127
+ # files mid-teardown; then strip MCP/env from agent settings; then remove
128
+ # the plist/unit + Claude plugin registration. State at ~/.weclaude is
129
+ # preserved unless `--purge` is given.
130
+ purge=0
131
+ [[ "${1:-}" == "--purge" ]] && purge=1
132
+ http_post /shutdown >/dev/null 2>&1 || true
133
+ NODE_BIN="$(command -v node)"
134
+ SYNC_SCRIPT="$REPO_ROOT/dist/cli/sync.js"
135
+ [[ -f "$SYNC_SCRIPT" ]] && "$NODE_BIN" "$SYNC_SCRIPT" --remove || true
136
+ bash "$REPO_ROOT/scripts/uninstall.sh"
137
+ rm -f "$HOME_DIR/.weclaude/sync.lock.json"
138
+ if (( purge )); then
139
+ rm -rf "$HOME_DIR/.weclaude"
140
+ echo "[weclaude] state purged (~/.weclaude removed)"
141
+ else
142
+ echo "[weclaude] cleaned. state kept at ~/.weclaude — remove with 'weclaude uninstall --purge' or 'rm -rf ~/.weclaude'"
143
+ fi
144
+ echo "[weclaude] safe to 'npm uninstall -g weclaude' now"
145
+ ;;
146
+ mirror)
147
+ cwd="${CLAUDE_PROJECT_DIR:-${CODEBUDDY_PROJECT_DIR:-$(pwd)}}"
148
+ # claude-internal stores under ~/.claude-internal/projects/, official claude
149
+ # under ~/.claude/projects/, codebuddy under ~/.codebuddy/projects/. Walk up
150
+ # parent dirs — caller may be in a subdirectory of the cwd the CLI was
151
+ # launched in (e.g. monorepo subpackage). Encoding differs: claude keeps a
152
+ # leading `-` ([/.]→-), codebuddy trims it first (no leading `-`).
153
+ # Prioritize the base matching the active CLI (detected via env vars) so a
154
+ # codebuddy session doesn't fall through to a stale claude project dir.
155
+ if [[ -n "${CODEBUDDY_SESSION_ID:-}" ]]; then
156
+ bases=("$HOME/.codebuddy/projects" "$HOME/.claude-internal/projects" "$HOME/.claude/projects")
157
+ else
158
+ bases=("$HOME/.claude-internal/projects" "$HOME/.claude/projects" "$HOME/.codebuddy/projects")
159
+ fi
160
+ proj=""
161
+ probe="$cwd"
162
+ while [[ -z "$proj" && -n "$probe" ]]; do
163
+ for base in "${bases[@]}"; do
164
+ if [[ "$base" == *codebuddy* ]]; then
165
+ enc="$(printf %s "$probe" | sed 's|^[/]*||; s|[/.]|-|g')"
166
+ else
167
+ enc="$(printf %s "$probe" | sed 's|[/.]|-|g')"
168
+ fi
169
+ if [[ -d "$base/$enc" ]]; then proj="$base/$enc"; break; fi
170
+ done
171
+ [[ "$probe" == "/" ]] && break
172
+ probe="$(dirname "$probe")"
173
+ done
174
+ if [[ -z "$proj" ]]; then
175
+ echo "no claude/codebuddy project dir for cwd: $cwd (or any ancestor)"
176
+ echo "tried encodings up to $HOME/.claude{,-internal}/projects/ + $HOME/.codebuddy/projects/<encoded-ancestor>"
177
+ exit 1
178
+ fi
179
+ # Resolve the *calling* session via env var the CLI injects into every
180
+ # child process. Both Claude Code and CodeBuddy export CLAUDE_SESSION_ID
181
+ # (back-compat); CODEBUDDY_SESSION_ID is the codebuddy-native name.
182
+ sid="${CLAUDE_CODE_SESSION_ID:-${CLAUDE_SESSION_ID:-${CODEBUDDY_SESSION_ID:-}}}"
183
+ latest=""
184
+ if [[ -n "$sid" && -f "$proj/$sid.jsonl" ]]; then
185
+ latest="$proj/$sid.jsonl"
186
+ fi
187
+ # Fallback for setups that don't propagate the env var: most-recent mtime.
188
+ [[ -z "$latest" ]] && latest=$(ls -t "$proj"/*.jsonl 2>/dev/null | head -1)
189
+ if [[ -z "$latest" ]]; then
190
+ echo "no .jsonl under $proj"; exit 1
191
+ fi
192
+ sid="$(basename "$latest" .jsonl)"
193
+ # Accept user-friendly prefixes: vid:<id> → user:<id>, chatid:<id> → chat:<id>.
194
+ # Pass anything else through (already in user:/chat:/group: form, or empty).
195
+ raw="${1:-}"
196
+ case "$raw" in
197
+ vid:*) target="user:${raw#vid:}" ;;
198
+ chatid:*) target="chat:${raw#chatid:}" ;;
199
+ *) target="$raw" ;;
200
+ esac
201
+ body=$(jq -nc --arg s "$sid" --arg p "$latest" --arg t "$target" --arg tp "${TMUX_PANE:-}" \
202
+ '{sessionId:$s, jsonlPath:$p}
203
+ + (if $t == "" then {} else {target:$t} end)
204
+ + (if $tp == "" then {} else {tmuxPane:$tp} end)')
205
+ http_post /mirror/attach "$body" | jq . 2>/dev/null || true
206
+ ;;
207
+ mirror-status)
208
+ http_get /mirror/status | jq . 2>/dev/null || http_get /mirror/status
209
+ ;;
210
+ sync)
211
+ NODE_BIN="$(command -v node)"
212
+ SCRIPT="$REPO_ROOT/dist/cli/sync.js"
213
+ [[ -f "$SCRIPT" ]] || { echo "build first: npm run build"; exit 1; }
214
+ exec "$NODE_BIN" "$SCRIPT" "$@"
215
+ ;;
216
+ unsync)
217
+ NODE_BIN="$(command -v node)"
218
+ SCRIPT="$REPO_ROOT/dist/cli/sync.js"
219
+ [[ -f "$SCRIPT" ]] || { echo "build first: npm run build"; exit 1; }
220
+ exec "$NODE_BIN" "$SCRIPT" --remove "$@"
221
+ ;;
222
+ audit)
223
+ NODE_BIN="$(command -v node)"
224
+ SCRIPT="$REPO_ROOT/dist/cli/audit.js"
225
+ [[ -f "$SCRIPT" ]] || { echo "build first: npm run build"; exit 1; }
226
+ exec "$NODE_BIN" "$SCRIPT" "$@"
227
+ ;;
228
+ help|-h|--help) usage ;;
229
+ version|-v|--version)
230
+ # Read version straight from the package manifest — no jq dep, regex is enough.
231
+ pkg="$REPO_ROOT/package.json"
232
+ [[ -f "$pkg" ]] || { echo "package.json not found at $pkg" >&2; exit 1; }
233
+ sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$pkg" | head -1
234
+ ;;
235
+ *) echo "unknown subcommand: $cmd"; usage; exit 2 ;;
236
+ esac
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: 统计当前会话(含子会话)的 token 用量与成本,不跨 /clear
3
+ allowed-tools: Bash(${CLAUDE_PLUGIN_ROOT}/cli/weclaude.sh:*)
4
+ argument-hint: [tag]
5
+ ---
6
+
7
+ !`${CLAUDE_PLUGIN_ROOT}/cli/weclaude.sh audit $ARGUMENTS`
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: WeCom remote control — mirror current Claude session to WeCom
3
+ allowed-tools: Bash(${CLAUDE_PLUGIN_ROOT}/cli/weclaude.sh:*)
4
+ argument-hint: [user:<userid>|chat:<chatid>]
5
+ ---
6
+
7
+ !`${CLAUDE_PLUGIN_ROOT}/cli/weclaude.sh mirror $ARGUMENTS`
@@ -0,0 +1,102 @@
1
+ // Declarative config for weclaude. Copy to ~/.weclaude/config.jsonc and edit.
2
+ // All paths support `~`. Secrets (botId/secret) can also live in ~/.weclaude/secrets.json
3
+ // (same shape, deep-merged on top — keep secrets out of dotfile repos).
4
+ {
5
+ "$schema": "./schema.json",
6
+
7
+ // ── WeCom bot credentials ─────────────────────────────────────────
8
+ "bot": {
9
+ "botId": "", // 智能机器人 ID
10
+ "secret": "", // 智能机器人 secret
11
+ "websocketUrl": "wss://openws.work.weixin.qq.com"
12
+ },
13
+
14
+ // ── Default chat target for proactive outbound ────────────────────
15
+ // Format: "user:<userid>" | "chat:<chatid>" | "group:<groupid>"
16
+ "defaultChat": "",
17
+
18
+ // ── Daemon (resident process) ─────────────────────────────────────
19
+ "daemon": {
20
+ "host": "127.0.0.1",
21
+ "port": 17890,
22
+ "stateDir": "~/.weclaude/state",
23
+ "logFile": "~/.weclaude/daemon.log",
24
+ "logLevel": "info" // trace|debug|info|warn|error
25
+ },
26
+
27
+ // ── /wrc remote-control behavior ──────────────────────────────────
28
+ "wrc": {
29
+ // Allow which WeCom users / chats to drive CC. Empty = deny all.
30
+ "allowFrom": [], // ["user:zhangsan", "chat:wcXXXX"]
31
+ // headless: spawn `claude -p` per msg with per-principal session map.
32
+ // mirror : attach to an existing interactive `claude` session by injecting
33
+ // via `claude --resume <sid> -p` AND tailing the same session
34
+ // jsonl, pushing live assistant output to WeCom.
35
+ "mode": "headless",
36
+ "claudeBin": "claude",
37
+ // CLI backend selection. Three built-ins: "claude" / "claude-internal" /
38
+ // "codebuddy". Empty/omitted → inferred from claudeBin basename (back-compat).
39
+ "defaultCli": "claude",
40
+ // Per-backend binary overrides (when the executable isn't on $PATH under
41
+ // its canonical name). Missing entry → built-in default.
42
+ // "cliBackends": {
43
+ // "codebuddy": { "bin": "/usr/local/bin/codebuddy" }
44
+ // },
45
+ "cwd": "~", // workdir (mirror: must match the running claude's cwd)
46
+ "sessionMapFile": "~/.weclaude/sessions.json",
47
+ "extraArgs": [], // appended to `claude -p`
48
+ "mirror": {
49
+ // Pin a session id; empty → auto-pick latest .jsonl mtime under
50
+ // ~/.claude/projects/<encoded(cwd)>/.
51
+ "sessionId": "",
52
+ // Where to push live assistant output. Empty → defaultChat.
53
+ "pushChat": "", // "user:<userid>" | "chat:<chatid>"
54
+ "chunkChars": 1800,
55
+ // Brief 模式: 每个 turn 群里只发两条 —— 「🧵 [本轮详情](url)」+ finish 文本。
56
+ // 中间 tool_use / tool_result / thinking 只写进详情页 (可折叠, Claude 网页风格)。
57
+ // 授权卡照常发群。
58
+ "brief": false
59
+ }
60
+ },
61
+
62
+ // ── PreToolUse approval (all tools → button card; align with auth-helper) ─
63
+ "approval": {
64
+ "enabled": true,
65
+ "matcher": ".*", // 全量拦截;Bash 只读命令(grep/ls/cat/...)走脚本 fast-path
66
+ "approvers": [], // ["user:zhangsan"] empty → defaultChat
67
+ "hookTimeoutSec": 43210, // hook curl --max-time (12h + 10s)
68
+ "longPollSec": 43200, // daemon long-poll (12h)
69
+ "sessionCacheMinutes": 30, // (session_id, tool, cmd_prefix_hash) → decision
70
+ "windowMinutes": 5, // 「⏱ N 分钟」按钮:开窗口期间所有请求自动 allow
71
+ "sensitiveArgRedact": true, // mask password/token-like values before sending to IM
72
+ "fallbackOnError": "ask" // never "deny" — never break workflow
73
+ },
74
+
75
+ // ── claude / codebuddy wrapper sync ───────────────────────────────
76
+ // `weclaude sync` writes hooks/MCP/permissions into every target settings.json
77
+ // listed below. Multi-CLI setups (claude + codebuddy 同时装) 列多项即可。
78
+ // kind: "claude" (含 claude-internal 等 fork) | "codebuddy"
79
+ // • claude-internal 通过 settingsPath: "~/.claude-internal/settings.json" 表达
80
+ "sync": {
81
+ "targets": [
82
+ {
83
+ "kind": "claude",
84
+ "settingsPath": "~/.claude/settings.json",
85
+ "scope": "user" // user | project | local
86
+ }
87
+ // 多 CLI 并存示例 (取消注释即可):
88
+ // ,{ "kind": "codebuddy", "settingsPath": "~/.codebuddy/settings.json", "scope": "user" }
89
+ // ,{ "kind": "claude", "settingsPath": "~/.claude-internal/settings.json", "scope": "user" }
90
+ ]
91
+ },
92
+
93
+ // ── Standalone detail relay (weclaude svr) ────────────────────────
94
+ // `weclaude svr` 读这里作为默认参数, CLI --host/--port 仍可覆盖。省略整段等于
95
+ // 用硬编码兜底 (0.0.0.0:17891)。改到 80 让详情页 URL 用标准端口 (需 root)。
96
+ "svr": {
97
+ "host": "0.0.0.0",
98
+ "port": 17891,
99
+ "stateDir": "~/.weclaude/svr",
100
+ "tokenFile": "~/.weclaude/svr-token"
101
+ }
102
+ }
@@ -0,0 +1,156 @@
1
+ // Slash-command entry: `weclaude audit [tag]`.
2
+ // - With a tag: ask the daemon for the newest-by-mtime mirror whose target
3
+ // carries `#<tag>` and audit THAT session (regardless of which CLI/pane
4
+ // the command was fired from). Fails hard when the daemon is down or no
5
+ // match — falling back to "current session" would silently mis-answer.
6
+ // - Without a tag: resolve the caller's own session (via
7
+ // $CLAUDE_CODE_SESSION_ID or newest-mtime jsonl under the project dir),
8
+ // print locally, and — when the daemon is up and this session is
9
+ // mirrored — also push the report into WeCom.
10
+ // WeCom-side `/audit` doesn't go through here; daemon/inbound.ts intercepts
11
+ // it and calls computeAuditReport directly (bypasses the flaky tmux paste path).
12
+ import { readdirSync, statSync, existsSync } from "node:fs";
13
+ import { basename, join } from "node:path";
14
+ import { homedir } from "node:os";
15
+ import { computeAuditReport } from "../daemon/audit.js";
16
+ const DAEMON_BASE = process.env.WECLAUDE_DAEMON_BASE || "http://127.0.0.1:17890";
17
+ const PROJECT_BASES = [
18
+ join(homedir(), ".claude-internal", "projects"),
19
+ join(homedir(), ".claude", "projects"),
20
+ join(homedir(), ".codebuddy", "projects"),
21
+ ];
22
+ // Encoding differs by backend: Claude Code uses `/`/`.`→`-` (leading `-` kept);
23
+ // CodeBuddy strips the leading separator first (no leading `-`).
24
+ const encodeFor = (base, probe) => {
25
+ if (base.includes(".codebuddy"))
26
+ return probe.replace(/^[/]+/, "").replace(/[/.]/g, "-");
27
+ return probe.replace(/[/.]/g, "-");
28
+ };
29
+ const findProjectDir = (cwd) => {
30
+ let probe = cwd;
31
+ while (probe) {
32
+ for (const base of PROJECT_BASES) {
33
+ const p = join(base, encodeFor(base, probe));
34
+ if (existsSync(p))
35
+ return p;
36
+ }
37
+ if (probe === "/")
38
+ break;
39
+ const parent = probe.slice(0, probe.lastIndexOf("/")) || "/";
40
+ if (parent === probe)
41
+ break;
42
+ probe = parent;
43
+ }
44
+ return null;
45
+ };
46
+ const resolveSessionId = (proj) => {
47
+ const envSid = process.env.CLAUDE_CODE_SESSION_ID || process.env.CLAUDE_SESSION_ID;
48
+ if (envSid && existsSync(join(proj, `${envSid}.jsonl`)))
49
+ return envSid;
50
+ let latest = null;
51
+ for (const f of readdirSync(proj)) {
52
+ if (!f.endsWith(".jsonl"))
53
+ continue;
54
+ const fp = join(proj, f);
55
+ try {
56
+ const st = statSync(fp);
57
+ if (!latest || st.mtimeMs > latest.mtime) {
58
+ latest = { sid: basename(f, ".jsonl"), mtime: st.mtimeMs };
59
+ }
60
+ }
61
+ catch { /* ignore */ }
62
+ }
63
+ return latest?.sid ?? null;
64
+ };
65
+ const expandHome = (p) => (p.startsWith("~/") ? join(homedir(), p.slice(2)) : p);
66
+ // Ask daemon for all live mirrors, then filter to targets carrying `#<tag>`
67
+ // and pick the newest by jsonl mtime. Returns null when daemon is unreachable
68
+ // or no mirror matches.
69
+ const findMirrorByTag = async (tag) => {
70
+ const wanted = tag.replace(/^#/, "");
71
+ try {
72
+ const res = await fetch(`${DAEMON_BASE}/mirror/status`, { signal: AbortSignal.timeout(2000) });
73
+ if (!res.ok)
74
+ return null;
75
+ const status = (await res.json());
76
+ const matches = (status.mirrors ?? []).filter((m) => (m.target.split("#")[1] ?? "") === wanted);
77
+ if (matches.length === 0)
78
+ return null;
79
+ const ranked = matches
80
+ .map((m) => {
81
+ let mt = 0;
82
+ try {
83
+ mt = statSync(expandHome(m.jsonlPath)).mtimeMs;
84
+ }
85
+ catch { /* ignore */ }
86
+ return { m, mt };
87
+ })
88
+ .sort((a, b) => b.mt - a.mt);
89
+ return ranked[0]?.m ?? null;
90
+ }
91
+ catch {
92
+ return null;
93
+ }
94
+ };
95
+ // Slash-command output goes into the jsonl as a `tool_result` under a user turn;
96
+ // mirror-bridge only tails `assistant` lines, so WeCom never sees it. Ask the
97
+ // daemon to push directly to whichever chat mirrors this session. Silent-fail
98
+ // when the daemon is down or no mirror is bound — local stdout is enough.
99
+ const pushToWeCom = async (sid, markdown) => {
100
+ try {
101
+ const statusRes = await fetch(`${DAEMON_BASE}/mirror/status`, { signal: AbortSignal.timeout(2000) });
102
+ if (!statusRes.ok)
103
+ return;
104
+ const status = (await statusRes.json());
105
+ const target = (status.mirrors ?? []).find((m) => m.sessionId === sid)?.target;
106
+ if (!target)
107
+ return;
108
+ await fetch(`${DAEMON_BASE}/message`, {
109
+ method: "POST",
110
+ headers: { "content-type": "application/json" },
111
+ body: JSON.stringify({ chat: target, markdown }),
112
+ signal: AbortSignal.timeout(5000),
113
+ });
114
+ }
115
+ catch { /* daemon unreachable — local stdout is enough */ }
116
+ };
117
+ const main = async () => {
118
+ const tag = (process.argv.slice(2).join(" ") || "").trim().replace(/^#/, "");
119
+ if (tag) {
120
+ const mirror = await findMirrorByTag(tag);
121
+ if (!mirror) {
122
+ console.error(`no mirror found for tag \`${tag}\` (daemon down or session not attached)`);
123
+ process.exit(1);
124
+ }
125
+ const jsonlPath = expandHome(mirror.jsonlPath);
126
+ if (!existsSync(jsonlPath)) {
127
+ console.error(`mirror jsonl missing: ${jsonlPath}`);
128
+ process.exit(1);
129
+ }
130
+ const report = computeAuditReport({ sessionId: mirror.sessionId, jsonlPath, tag });
131
+ console.log(report);
132
+ await pushToWeCom(mirror.sessionId, report);
133
+ return;
134
+ }
135
+ const cwd = process.env.CLAUDE_PROJECT_DIR || process.cwd();
136
+ const proj = findProjectDir(cwd);
137
+ if (!proj) {
138
+ console.error(`no claude project dir for cwd: ${cwd}`);
139
+ process.exit(1);
140
+ }
141
+ const sid = resolveSessionId(proj);
142
+ if (!sid) {
143
+ console.error(`no .jsonl under ${proj}`);
144
+ process.exit(1);
145
+ }
146
+ const jsonlPath = join(proj, `${sid}.jsonl`);
147
+ if (!existsSync(jsonlPath)) {
148
+ console.error(`main jsonl missing: ${jsonlPath}`);
149
+ process.exit(1);
150
+ }
151
+ const report = computeAuditReport({ sessionId: sid, jsonlPath });
152
+ console.log(report);
153
+ await pushToWeCom(sid, report);
154
+ };
155
+ main();
156
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../cli/audit.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,2EAA2E;AAC3E,2DAA2D;AAC3D,4EAA4E;AAC5E,qEAAqE;AACrE,kDAAkD;AAClD,4EAA4E;AAC5E,iFAAiF;AACjF,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,wBAAwB,CAAC;AAEjF,MAAM,aAAa,GAAG;IACpB,IAAI,CAAC,OAAO,EAAE,EAAE,kBAAkB,EAAE,UAAU,CAAC;IAC/C,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC;IACtC,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,UAAU,CAAC;CAC1C,CAAC;AAEF,gFAAgF;AAChF,iEAAiE;AACjE,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE;IACxD,IAAI,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACzF,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AACrC,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,GAAW,EAAiB,EAAE;IACpD,IAAI,KAAK,GAAG,GAAG,CAAC;IAChB,OAAO,KAAK,EAAE,CAAC;QACb,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;YAC7C,IAAI,UAAU,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC9B,CAAC;QACD,IAAI,KAAK,KAAK,GAAG;YAAE,MAAM;QACzB,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC;QAC7D,IAAI,MAAM,KAAK,KAAK;YAAE,MAAM;QAC5B,KAAK,GAAG,MAAM,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAiB,EAAE;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IACnF,IAAI,MAAM,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,MAAM,QAAQ,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IACvE,IAAI,MAAM,GAA0C,IAAI,CAAC;IACzD,KAAK,MAAM,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QAClC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,SAAS;QACpC,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;YACxB,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;gBACzC,MAAM,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC;YAC7D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAIjG,4EAA4E;AAC5E,8EAA8E;AAC9E,wBAAwB;AACxB,MAAM,eAAe,GAAG,KAAK,EAAE,GAAW,EAA6B,EAAE;IACvE,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,WAAW,gBAAgB,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/F,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA8B,CAAC;QAC/D,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC;QAChG,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO;aACnB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACT,IAAI,EAAE,GAAG,CAAC,CAAC;YACX,IAAI,CAAC;gBAAC,EAAE,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;YAC9E,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;QACnB,CAAC,CAAC;aACD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/B,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,IAAI,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,IAAI,CAAC;IAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,iFAAiF;AACjF,8EAA8E;AAC9E,8EAA8E;AAC9E,0EAA0E;AAC1E,MAAM,WAAW,GAAG,KAAK,EAAE,GAAW,EAAE,QAAgB,EAAiB,EAAE;IACzE,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,GAAG,WAAW,gBAAgB,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrG,IAAI,CAAC,SAAS,CAAC,EAAE;YAAE,OAAO;QAC1B,MAAM,MAAM,GAAG,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,CAA8B,CAAC;QACrE,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,GAAG,CAAC,EAAE,MAAM,CAAC;QAC/E,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,KAAK,CAAC,GAAG,WAAW,UAAU,EAAE;YACpC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;YAChD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC,CAAC,iDAAiD,CAAC,CAAC;AAC/D,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;IACrC,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAE7E,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,KAAK,CAAC,6BAA6B,GAAG,0CAA0C,CAAC,CAAC;YAC1F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,yBAAyB,SAAS,EAAE,CAAC,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,MAAM,GAAG,kBAAkB,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC;QACnF,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,MAAM,WAAW,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC5C,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC5D,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,kCAAkC,GAAG,EAAE,CAAC,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,GAAG,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,QAAQ,CAAC,CAAC;IAC7C,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,KAAK,CAAC,uBAAuB,SAAS,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,kBAAkB,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpB,MAAM,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AACjC,CAAC,CAAC;AAEF,IAAI,EAAE,CAAC"}