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.
- package/.claude-plugin/marketplace.json +14 -0
- package/.claude-plugin/plugin.json +14 -0
- package/CLAUDE.md +83 -0
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/cli/weclaude.sh +236 -0
- package/commands/audit.md +7 -0
- package/commands/wrc.md +7 -0
- package/config.example.jsonc +102 -0
- package/dist/cli/audit.js +156 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/init.js +311 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/sync.js +175 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/daemon/approval.js +1301 -0
- package/dist/daemon/approval.js.map +1 -0
- package/dist/daemon/ask.js +101 -0
- package/dist/daemon/ask.js.map +1 -0
- package/dist/daemon/audit.js +168 -0
- package/dist/daemon/audit.js.map +1 -0
- package/dist/daemon/cc-bridge.js +204 -0
- package/dist/daemon/cc-bridge.js.map +1 -0
- package/dist/daemon/cfg-sync.js +240 -0
- package/dist/daemon/cfg-sync.js.map +1 -0
- package/dist/daemon/claim.js +95 -0
- package/dist/daemon/claim.js.map +1 -0
- package/dist/daemon/detail.js +153 -0
- package/dist/daemon/detail.js.map +1 -0
- package/dist/daemon/graph.js +176 -0
- package/dist/daemon/graph.js.map +1 -0
- package/dist/daemon/http.js +83 -0
- package/dist/daemon/http.js.map +1 -0
- package/dist/daemon/inbound.js +919 -0
- package/dist/daemon/inbound.js.map +1 -0
- package/dist/daemon/index.js +569 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/last-response.js +59 -0
- package/dist/daemon/last-response.js.map +1 -0
- package/dist/daemon/mirror-bridge.js +3512 -0
- package/dist/daemon/mirror-bridge.js.map +1 -0
- package/dist/daemon/mirror-store.js +39 -0
- package/dist/daemon/mirror-store.js.map +1 -0
- package/dist/daemon/outbound.js +42 -0
- package/dist/daemon/outbound.js.map +1 -0
- package/dist/daemon/peers.js +146 -0
- package/dist/daemon/peers.js.map +1 -0
- package/dist/daemon/pending.js +90 -0
- package/dist/daemon/pending.js.map +1 -0
- package/dist/daemon/quota.js +164 -0
- package/dist/daemon/quota.js.map +1 -0
- package/dist/daemon/redact.js +27 -0
- package/dist/daemon/redact.js.map +1 -0
- package/dist/daemon/session-cache.js +149 -0
- package/dist/daemon/session-cache.js.map +1 -0
- package/dist/daemon/session-scan.js +273 -0
- package/dist/daemon/session-scan.js.map +1 -0
- package/dist/daemon/sessions.js +35 -0
- package/dist/daemon/sessions.js.map +1 -0
- package/dist/daemon/spawn-tmux.js +213 -0
- package/dist/daemon/spawn-tmux.js.map +1 -0
- package/dist/daemon/tips.js +25 -0
- package/dist/daemon/tips.js.map +1 -0
- package/dist/daemon/topics.js +197 -0
- package/dist/daemon/topics.js.map +1 -0
- package/dist/daemon/usage.js +358 -0
- package/dist/daemon/usage.js.map +1 -0
- package/dist/daemon/wedoc.js +210 -0
- package/dist/daemon/wedoc.js.map +1 -0
- package/dist/daemon/ws.js +67 -0
- package/dist/daemon/ws.js.map +1 -0
- package/dist/mcp/server.js +385 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/shared/ansi.js +134 -0
- package/dist/shared/ansi.js.map +1 -0
- package/dist/shared/chat-http.js +195 -0
- package/dist/shared/chat-http.js.map +1 -0
- package/dist/shared/chat-render.js +53 -0
- package/dist/shared/chat-render.js.map +1 -0
- package/dist/shared/chat-view.js +116 -0
- package/dist/shared/chat-view.js.map +1 -0
- package/dist/shared/cli-backends.js +304 -0
- package/dist/shared/cli-backends.js.map +1 -0
- package/dist/shared/config-writer.js +39 -0
- package/dist/shared/config-writer.js.map +1 -0
- package/dist/shared/config.js +278 -0
- package/dist/shared/config.js.map +1 -0
- package/dist/shared/detail-render.js +836 -0
- package/dist/shared/detail-render.js.map +1 -0
- package/dist/shared/detail-store.js +205 -0
- package/dist/shared/detail-store.js.map +1 -0
- package/dist/shared/highlight.js +154 -0
- package/dist/shared/highlight.js.map +1 -0
- package/dist/shared/lan-ip.js +35 -0
- package/dist/shared/lan-ip.js.map +1 -0
- package/dist/shared/log.js +18 -0
- package/dist/shared/log.js.map +1 -0
- package/dist/shared/md-chunk.js +99 -0
- package/dist/shared/md-chunk.js.map +1 -0
- package/dist/shared/paths.js +12 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/shared/session-label.js +67 -0
- package/dist/shared/session-label.js.map +1 -0
- package/dist/shared/web-assets.js +51 -0
- package/dist/shared/web-assets.js.map +1 -0
- package/dist/svr/index.js +246 -0
- package/dist/svr/index.js.map +1 -0
- package/hooks/hooks.json +16 -0
- package/hooks/pre-tool-use.sh +167 -0
- package/launchd/com.weclaude.daemon.plist.template +45 -0
- package/package.json +97 -0
- package/scripts/install.sh +73 -0
- package/scripts/postinstall.sh +12 -0
- package/scripts/uninstall.sh +44 -0
- package/systemd/weclaude.service.template +17 -0
- package/web/chat.css +171 -0
- package/web/chat.js +367 -0
package/cli/weclaude.sh
ADDED
|
@@ -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
|
package/commands/wrc.md
ADDED
|
@@ -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":"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"}
|