agentradar-cli 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/setup.js +1 -1
- package/dist/vendor-runner/supervisor/supervise-agent.sh +21 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npx -y agentradar-cli runner start <runner-id>
|
|
|
11
11
|
npx -y agentradar-cli skill install
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
The setup command prints a short approval code. Approve that code in the AgentRadar vendor portal; the CLI then receives the AgentRadar bearer in the polling response body and writes the project MCP entry plus the supported runner's private config under `~/.agentradar`, with secret-bearing files mode `0600`. It prints the paths and the exact runner start command. It never prints the bearer or puts it in a URL, command argument, or repository file. The supervisor stages credentials into private runtime files, scrubs its child environment, and gives Claude Code only the strict AgentRadar MCP config.
|
|
14
|
+
The setup command prints a short approval code. Approve that code in the AgentRadar vendor portal; the CLI then receives the AgentRadar bearer in the polling response body and writes the project MCP entry plus the supported runner's private config under `~/.agentradar`, with secret-bearing files mode `0600`. It prints the paths and the exact runner start command. It never prints the bearer or puts it in a URL, command argument, or repository file. The supervisor stages only AgentRadar and vendor-tool credentials into private runtime files, scrubs its child environment, and gives Claude Code only the strict AgentRadar MCP config.
|
|
15
15
|
|
|
16
16
|
Claude Code asks you to trust the folder on first launch. `setup` does not pre-accept that for you, and the onboarding lines above deliberately do not: the `hasTrustDialogAccepted` flag trusts everything already in the directory — existing hooks, `.claude/settings.json`, skills — not just the MCP entry setup added. Dismiss the prompt yourself.
|
|
17
17
|
|
|
@@ -19,4 +19,4 @@ There is a `--trust` flag for unattended runners, where a launch prompt would ha
|
|
|
19
19
|
|
|
20
20
|
The skill command installs only the package-bundled `agentradar-vendor` markdown skill at `.claude/skills/agentradar-vendor/SKILL.md` in the current project. It does not fetch or require access to the private AgentRadar repository.
|
|
21
21
|
|
|
22
|
-
Claude authentication stays separate. On macOS, the runner
|
|
22
|
+
Claude authentication stays separate. On macOS, the runner uses the vendor's existing Keychain-backed Claude Code login through the normal user config path; it never copies, extracts, or injects Claude credentials. `CLAUDE_CODE_OAUTH_TOKEN` is intentionally unset before launch because the environment-token contract does not authenticate the interactive TUI. `ANTHROPIC_API_KEY` is prohibited and the supervisor refuses to start when it is set. Connector isolation remains enforced by `--strict-mcp-config` with the private AgentRadar MCP file.
|
package/dist/setup.js
CHANGED
|
@@ -205,7 +205,7 @@ export async function setupVendorAgent(deps = {}) {
|
|
|
205
205
|
: `Claude Code will ask you to trust ${projectPath} on first launch — approve it there. Not pre-accepted, because that would trust everything already in the folder, not just this MCP. Re-run with --trust for unattended onboarding.`);
|
|
206
206
|
log(`Runner installed → ${runnerEnv}`);
|
|
207
207
|
log(`Start it with: agentradar runner start ${approved.runnerId}`);
|
|
208
|
-
log("On macOS the runner uses your existing Claude Code Keychain
|
|
208
|
+
log("On macOS the runner uses your existing Claude Code Keychain-backed login. CLAUDE_CODE_OAUTH_TOKEN is ignored; ANTHROPIC_API_KEY is prohibited.");
|
|
209
209
|
return;
|
|
210
210
|
}
|
|
211
211
|
throw new Error("AgentRadar setup code expired; run setup again");
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
#
|
|
10
10
|
# 1. a tmux session ($TMUX_SESSION) running the agent:
|
|
11
11
|
# claude --continue $CLAUDE_EXTRA_FLAGS --strict-mcp-config --mcp-config=$MCP_CONFIG
|
|
12
|
-
# in $AR_BASE, with a SCRUBBED allowlist env +
|
|
13
|
-
#
|
|
12
|
+
# in $AR_BASE, with a SCRUBBED allowlist env + the macOS user's normal
|
|
13
|
+
# Keychain-backed Claude session. Strict MCP flags allow ONLY AgentRadar's config;
|
|
14
14
|
# 2. the feeder ($FEEDER_JS) OUTSIDE that session, holding $AWP_BEARER, long-
|
|
15
15
|
# polling next_event and tmux-injecting each event into the pane.
|
|
16
16
|
#
|
|
@@ -51,14 +51,8 @@ set +a
|
|
|
51
51
|
: "${AR_BASE:?AR_BASE required (the agent working dir)}"
|
|
52
52
|
: "${AWP_MCP_URL:?AWP_MCP_URL required}"
|
|
53
53
|
: "${AWP_BEARER:?AWP_BEARER required}"
|
|
54
|
-
# CLAUDE_CODE_OAUTH_TOKEN is an OPTIONAL explicit override. When unset on macOS, section 2b extracts
|
|
55
|
-
# claudeAiOauth.accessToken from Claude Code's existing Keychain credential into the same private-file
|
|
56
|
-
# handoff. With neither source, Claude uses its normal interactive auth path. ANTHROPIC_API_KEY remains
|
|
57
|
-
# hard-refused below.
|
|
58
|
-
: "${CLAUDE_CODE_OAUTH_TOKEN:=}"
|
|
59
54
|
: "${MCP_CONFIG:?MCP_CONFIG required (path to awp-only.json)}"
|
|
60
55
|
TMUX_SESSION="${TMUX_SESSION:-${AGENT_NAME}}"
|
|
61
|
-
CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR:-${HOME}/.agentradar/config/${AGENT_NAME}}"
|
|
62
56
|
FEEDER_JS="${FEEDER_JS:-$(cd "$(dirname "$0")/.." && pwd)/feeder.mjs}"
|
|
63
57
|
PANE_LIVE_SH="${PANE_LIVE_SH:-$(cd "$(dirname "$0")/.." && pwd)/pane-live.sh}"
|
|
64
58
|
# #133 M4 live terminal — the OPTIONAL feeder bridge (streams the tmux pane to the portals). It's an
|
|
@@ -111,6 +105,11 @@ if [ -n "${AGENT_TOOL_ENV_FILE}" ]; then
|
|
|
111
105
|
log "loaded ${#TOOL_ENV_ARGS[@]} tool-env var(s) from ${AGENT_TOOL_ENV_FILE}"
|
|
112
106
|
fi
|
|
113
107
|
|
|
108
|
+
# Interactive Claude must use the user's persisted Keychain-backed login. An isolated config or an
|
|
109
|
+
# environment OAuth token selects a different auth contract and can put the TUI back into onboarding.
|
|
110
|
+
# env -i below still scrubs everything else; strict MCP flags, not CLAUDE_CONFIG_DIR, isolate connectors.
|
|
111
|
+
unset CLAUDE_CONFIG_DIR CLAUDE_CODE_OAUTH_TOKEN
|
|
112
|
+
|
|
114
113
|
# HARD RULE — OAuth only, never a pay-as-you-go API key.
|
|
115
114
|
[ -z "${ANTHROPIC_API_KEY:-}" ] || die "ANTHROPIC_API_KEY is set — refuse to start (claude.ai OAuth only)"
|
|
116
115
|
|
|
@@ -119,15 +118,12 @@ command -v "${CLAUDE_BIN}" >/dev/null 2>&1 || die "claude CLI not found (${CLAUD
|
|
|
119
118
|
[ -f "${FEEDER_JS}" ] || die "feeder not found: ${FEEDER_JS}"
|
|
120
119
|
[ -f "${PANE_LIVE_SH}" ] || die "pane liveness check not found: ${PANE_LIVE_SH}"
|
|
121
120
|
[ -d "${AR_BASE}" ] || die "AR_BASE dir not found: ${AR_BASE}"
|
|
122
|
-
mkdir -p "${CLAUDE_CONFIG_DIR}"
|
|
123
121
|
|
|
124
122
|
# --- 2b. Credential hygiene (#152): secrets to 0600 files, OUT of argv+env ------
|
|
125
|
-
# The AWP bearer
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
#
|
|
129
|
-
# Then we UNSET the raw vars from this supervisor's env so neither the supervisor nor the feeder it
|
|
130
|
-
# spawns inherits them. (The agent's own outbound bearer already lives only in awp-only.json @0600.)
|
|
123
|
+
# The AWP bearer must not linger in any long-lived process's ARGV (`ps aux`) or ENVIRONMENT
|
|
124
|
+
# (`ps eww`), where a co-resident process on the box could read it. Stage it into a per-agent 0600
|
|
125
|
+
# file and hand the feeder only the FILE PATH. Claude authenticates directly through the user's normal
|
|
126
|
+
# macOS Keychain-backed session; no Claude credential is copied, extracted, or injected.
|
|
131
127
|
_SECRETS_DIR="${HOME}/.agentradar/run/${AGENT_NAME}"
|
|
132
128
|
( umask 077; mkdir -p "${_SECRETS_DIR}" )
|
|
133
129
|
chmod 700 "${_SECRETS_DIR}" 2>/dev/null || true
|
|
@@ -135,34 +131,12 @@ _BEARER_FILE="${_SECRETS_DIR}/awp_bearer"
|
|
|
135
131
|
( umask 077; printf '%s' "${AWP_BEARER}" > "${_BEARER_FILE}" )
|
|
136
132
|
AWP_BEARER_FILE="${_BEARER_FILE}"
|
|
137
133
|
unset AWP_BEARER # drop from the supervisor's exported env → feeder no longer inherits it; ps eww clean
|
|
138
|
-
# OAuth token: same treatment. An explicit CLAUDE_CODE_OAUTH_TOKEN still wins. Otherwise, on macOS,
|
|
139
|
-
# Claude Code stores a JSON credential in the "Claude Code-credentials" Keychain item; the env contract
|
|
140
|
-
# needs only its claudeAiOauth.accessToken scalar, not that whole JSON copied into .credentials.json.
|
|
141
|
-
# Pipe Keychain → plutil → a 0600 file so the token never appears in logs, argv, or this supervisor's env.
|
|
142
|
-
_OAUTH_TOKEN_FILE="${_SECRETS_DIR}/oauth_token"
|
|
143
|
-
if [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]; then
|
|
144
|
-
( umask 077; printf '%s' "${CLAUDE_CODE_OAUTH_TOKEN}" > "${_OAUTH_TOKEN_FILE}" )
|
|
145
|
-
elif [ "$(uname -s)" = "Darwin" ] && [ -x /usr/bin/security ] && [ -x /usr/bin/plutil ]; then
|
|
146
|
-
_OAUTH_TOKEN_TMP="${_OAUTH_TOKEN_FILE}.$$"
|
|
147
|
-
if ( umask 077; /usr/bin/security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null \
|
|
148
|
-
| /usr/bin/plutil -extract claudeAiOauth.accessToken raw -o "${_OAUTH_TOKEN_TMP}" - 2>/dev/null ) \
|
|
149
|
-
&& [ -s "${_OAUTH_TOKEN_TMP}" ]; then
|
|
150
|
-
mv "${_OAUTH_TOKEN_TMP}" "${_OAUTH_TOKEN_FILE}"
|
|
151
|
-
log "loaded Claude Code OAuth credential from macOS Keychain"
|
|
152
|
-
else
|
|
153
|
-
rm -f "${_OAUTH_TOKEN_TMP}"
|
|
154
|
-
_OAUTH_TOKEN_FILE=""
|
|
155
|
-
fi
|
|
156
|
-
else
|
|
157
|
-
_OAUTH_TOKEN_FILE=""
|
|
158
|
-
fi
|
|
159
|
-
unset CLAUDE_CODE_OAUTH_TOKEN # out of the supervisor+feeder env (ps eww); staged to the 0600 file above
|
|
160
134
|
# Vendor MCP-tool creds (#166): the agent's OWN tool API keys (WEBFLOW_TOKEN, PERPLEXITY_API_KEY, …) were
|
|
161
135
|
# being passed INLINE as KEY=VALUE on the env -i line → stored PLAINTEXT in tmux's pane_start_command
|
|
162
136
|
# (`tmux list-panes` = any co-resident proc) and in claude's env (ps eww). Same inline-secret class as the
|
|
163
137
|
# bearer/token. Stage the already-validated entries to a 0600 file and pass ONLY AR_TOOL_ENV_FILE (a PATH);
|
|
164
138
|
# the wrapper reads+exports them just before exec, so the VALUES never appear on the pane command / argv
|
|
165
|
-
# (
|
|
139
|
+
# (tool credentials remain in Claude's environment because their MCP subprocesses require them).
|
|
166
140
|
_TOOL_ENV_FILE=""
|
|
167
141
|
if [ "${#TOOL_ENV_ARGS[@]}" -gt 0 ]; then
|
|
168
142
|
_TOOL_ENV_FILE="${_SECRETS_DIR}/tool_env"
|
|
@@ -231,29 +205,22 @@ ensure_agent() {
|
|
|
231
205
|
fi
|
|
232
206
|
# SCRUBBED launch: env -i with a strict allowlist so the agent inherits nothing from this daemon
|
|
233
207
|
# (AWP_BEARER, AWP_MCP_URL, etc. never reach the pane). CONNECTOR ISOLATION is enforced by
|
|
234
|
-
# --strict-mcp-config (the agent loads ONLY $MCP_CONFIG's servers)
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
# NOT inlined here anymore (#166): they were staged to a 0600 file in 2b and are passed only as the PATH
|
|
208
|
+
# --strict-mcp-config (the agent loads ONLY $MCP_CONFIG's servers). Claude's config override stays unset,
|
|
209
|
+
# so the normal HOME/USER Keychain-backed interactive session and conversation history remain available.
|
|
210
|
+
# The agent's own tool creds (from AGENT_TOOL_ENV_FILE) are NOT inlined here anymore (#166): they were staged to a 0600 file in 2b and are passed only as the PATH
|
|
238
211
|
# AR_TOOL_ENV_FILE, which the wrapper reads+exports before exec — so no tool VALUE appears on the env -i
|
|
239
212
|
# argv / tmux pane_start_command. Reserved keys were already rejected at tool-file parse time (2b).
|
|
240
213
|
log "starting agent in tmux '${TMUX_SESSION}' (flags: ${CLAUDE_EXTRA_FLAGS:-none}; tool-env file: ${_TOOL_ENV_FILE:-none})"
|
|
241
214
|
# SC2086: CLAUDE_EXTRA_FLAGS is intentionally UNQUOTED so "--chrome" (etc.) word-splits into flags.
|
|
242
|
-
# _tool_env
|
|
215
|
+
# _tool_env uses the ${arr[@]+"${arr[@]}"} form so an EMPTY array is safe under `set -u` (bash 3.2).
|
|
243
216
|
# First-launch handling (#153/H4 durable-feeder): `claude --continue` EXITS 1 when the cwd has NO prior
|
|
244
|
-
# conversation (a fresh engagement folder
|
|
217
|
+
# conversation (for example, a fresh engagement folder) → the pane dies → the supervisor
|
|
245
218
|
# crash-loops. So try `--continue`, and FALL BACK to a fresh `claude` (which stays up AND creates the
|
|
246
219
|
# session) when there's nothing to resume. Self-healing: once a conversation exists, --continue resumes it;
|
|
247
220
|
# if it's ever lost, we cleanly start fresh instead of exit-looping. (--continue exits 1 on no-session,
|
|
248
221
|
# verified — so the `||` fallback is reliable.) The `/bin/sh -c` wrapper keeps EXTRA_FLAGS word-splitting.
|
|
249
222
|
# shellcheck disable=SC2086
|
|
250
223
|
_launch_flags=( ${CLAUDE_EXTRA_FLAGS} --strict-mcp-config --mcp-config="${MCP_CONFIG}" )
|
|
251
|
-
# OAuth (#152): section 2b staged either the explicit token or the macOS Keychain accessToken into a
|
|
252
|
-
# 0600 file and kept the value out of the supervisor+feeder env. Here we pass only that file PATH through
|
|
253
|
-
# env -i; the wrapper reads CLAUDE_CODE_OAUTH_TOKEN immediately before exec'ing Claude. If neither source
|
|
254
|
-
# exists, _OAUTH_TOKEN_FILE is empty and Claude falls back to its normal interactive auth path.
|
|
255
|
-
_oauth_env=()
|
|
256
|
-
[ -n "${_OAUTH_TOKEN_FILE}" ] && _oauth_env=( AR_OAUTH_TOKEN_FILE="${_OAUTH_TOKEN_FILE}" )
|
|
257
224
|
# #166: pass the tool creds as a FILE PATH (AR_TOOL_ENV_FILE), never inline KEY=VALUE on env -i.
|
|
258
225
|
_tool_env=()
|
|
259
226
|
[ -n "${_TOOL_ENV_FILE}" ] && _tool_env=( AR_TOOL_ENV_FILE="${_TOOL_ENV_FILE}" )
|
|
@@ -285,8 +252,8 @@ ensure_agent() {
|
|
|
285
252
|
LANG="${LANG:-en_US.UTF-8}" LC_ALL="${LC_ALL:-}" LC_CTYPE="${AR_AGENT_LC_CTYPE:-en_US.UTF-8}" \
|
|
286
253
|
TMPDIR="${TMPDIR:-/tmp}" NODE_OPTIONS="${NODE_OPTIONS:-}" \
|
|
287
254
|
DISABLE_AUTOUPDATER=1 \
|
|
288
|
-
${_tool_env[@]+"${_tool_env[@]}"}
|
|
289
|
-
/bin/sh -c 'if [ -n "${AR_TOOL_ENV_FILE:-}" ]; then while IFS= read -r _l || [ -n "$_l" ]; do case "$_l" in ""|\#*) continue ;; esac; _k=${_l%%=*}; _v=${_l#*=}; case "$_k" in HOME|PATH|USER|LOGNAME|SHELL|TERM|COLORTERM|LANG|LC_ALL|LC_CTYPE|TMPDIR|NODE_OPTIONS|CLAUDE_CONFIG_DIR|CLAUDE_CODE_OAUTH_TOKEN|AR_OAUTH_TOKEN_FILE|AR_TOOL_ENV_FILE|ANTHROPIC_API_KEY|AWP_BEARER|AWP_BEARER_FILE|AWP_MCP_URL|TMUX_SESSION|AGENT_PID_PATTERN) continue ;; esac; export "$_k=$_v"; done < "$AR_TOOL_ENV_FILE"; unset AR_TOOL_ENV_FILE _l _k _v; fi;
|
|
255
|
+
${_tool_env[@]+"${_tool_env[@]}"} \
|
|
256
|
+
/bin/sh -c 'if [ -n "${AR_TOOL_ENV_FILE:-}" ]; then while IFS= read -r _l || [ -n "$_l" ]; do case "$_l" in ""|\#*) continue ;; esac; _k=${_l%%=*}; _v=${_l#*=}; case "$_k" in HOME|PATH|USER|LOGNAME|SHELL|TERM|COLORTERM|LANG|LC_ALL|LC_CTYPE|TMPDIR|NODE_OPTIONS|CLAUDE_CONFIG_DIR|CLAUDE_CODE_OAUTH_TOKEN|AR_OAUTH_TOKEN_FILE|AR_TOOL_ENV_FILE|ANTHROPIC_API_KEY|AWP_BEARER|AWP_BEARER_FILE|AWP_MCP_URL|TMUX_SESSION|AGENT_PID_PATTERN) continue ;; esac; export "$_k=$_v"; done < "$AR_TOOL_ENV_FILE"; unset AR_TOOL_ENV_FILE _l _k _v; fi; CB="$1"; shift; "$CB" --continue "$@" || exec "$CB" "$@"' sh "${CLAUDE_BIN}" "${_launch_flags[@]}" || {
|
|
290
257
|
log "tmux could not start agent session '${TMUX_SESSION}'"
|
|
291
258
|
return 1
|
|
292
259
|
}
|
|
@@ -301,7 +268,7 @@ FEEDER_PID=""
|
|
|
301
268
|
cleanup() {
|
|
302
269
|
[ -n "${FEEDER_PID}" ] && kill "${FEEDER_PID}" 2>/dev/null || true
|
|
303
270
|
[ -n "${BRIDGE_PID:-}" ] && kill "${BRIDGE_PID}" 2>/dev/null || true # #133 terminal bridge
|
|
304
|
-
# #152: remove the staged 0600 secret files
|
|
271
|
+
# #152: remove the staged 0600 secret files on exit.
|
|
305
272
|
[ -n "${_SECRETS_DIR:-}" ] && rm -rf "${_SECRETS_DIR}" 2>/dev/null || true
|
|
306
273
|
}
|
|
307
274
|
trap cleanup EXIT INT TERM
|
|
@@ -315,8 +282,7 @@ log "starting feeder (${FEEDER_JS}) → session '${TMUX_SESSION}'"
|
|
|
315
282
|
# #152: pass AWP_BEARER_FILE (a 0600 PATH), NEVER the raw AWP_BEARER — the feeder reads the file, so the
|
|
316
283
|
# bearer is not in the feeder process's environment (`ps eww`) or argv. AWP_MCP_URL is not a secret.
|
|
317
284
|
AWP_MCP_URL="${AWP_MCP_URL}" AWP_BEARER_FILE="${AWP_BEARER_FILE}" TMUX_SESSION="${TMUX_SESSION}" \
|
|
318
|
-
AGENT_PID_PATTERN="${AGENT_PID_PATTERN}" CLAUDE_BIN="${CLAUDE_BIN}"
|
|
319
|
-
AR_OAUTH_TOKEN_FILE="${_OAUTH_TOKEN_FILE}" node "${FEEDER_JS}" &
|
|
285
|
+
AGENT_PID_PATTERN="${AGENT_PID_PATTERN}" CLAUDE_BIN="${CLAUDE_BIN}" node "${FEEDER_JS}" &
|
|
320
286
|
FEEDER_PID=$!
|
|
321
287
|
log "feeder pid ${FEEDER_PID}"
|
|
322
288
|
|