agentradar-cli 0.1.6 → 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 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. Preferred: use interactive `/login` with the vendor's claude.ai account. A `CLAUDE_CODE_OAUTH_TOKEN` may be stored in the private runner env file when non-interactive Claude authentication is required; the supervisor stages it into a mode-`0600` runtime file before launch. `ANTHROPIC_API_KEY` is prohibited and the supervisor refuses to start when it is set.
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("Claude Code will use your existing /login session. ANTHROPIC_API_KEY is prohibited; a securely stored CLAUDE_CODE_OAUTH_TOKEN is also supported.");
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 + a FRESH $CLAUDE_CONFIG_DIR
13
- # (so the agent sees ONLY the AgentRadar MCP — no operator connectors);
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 OPTIONAL. If SET → claude uses the setup-token (bills "API Usage",
55
- # subject to the org spend cap). If UNSET → claude falls through to SUBSCRIPTION OAuth (keychain login,
56
- # a one-time `/login`) → draws on the Max plan's included usage. Either way it's claude.ai auth only —
57
- # ANTHROPIC_API_KEY is still hard-refused below (never a pay-as-you-go key).
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 (feeder) and — when used — the OAuth setup-token (claude) must not linger in any
126
- # long-lived process's ARGV (`ps aux`) or ENVIRONMENT (`ps eww`), where a co-resident process on the
127
- # box could read them. We stage them into per-agent 0600 files and hand each consumer the FILE PATH
128
- # (not a secret): the feeder reads AWP_BEARER_FILE; the scrubbed claude wrapper reads AR_OAUTH_TOKEN_FILE.
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,24 +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 setup-token: same treatment. `set -a; . ENV_FILE` EXPORTS CLAUDE_CODE_OAUTH_TOKEN into the
139
- # supervisor env, from which the feeder (spawned WITHOUT env -i) would inherit it → ps eww leak. So when
140
- # it's set, stage it to a 0600 file and UNSET the raw var here (mirror of the bearer). ensure_agent then
141
- # passes ONLY AR_OAUTH_TOKEN_FILE (the path) on the scrubbed env -i line, and the sh wrapper reads it into
142
- # claude's env just before exec. When UNSET (subscription-login mode) _OAUTH_TOKEN_FILE stays empty → the
143
- # claude launch is a pure no-op and falls through to subscription OAuth (no API-billing regression).
144
- _OAUTH_TOKEN_FILE=""
145
- if [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]; then
146
- _OAUTH_TOKEN_FILE="${_SECRETS_DIR}/oauth_token"
147
- ( umask 077; printf '%s' "${CLAUDE_CODE_OAUTH_TOKEN}" > "${_OAUTH_TOKEN_FILE}" )
148
- fi
149
- unset CLAUDE_CODE_OAUTH_TOKEN # out of the supervisor+feeder env (ps eww); staged to the 0600 file above
150
134
  # Vendor MCP-tool creds (#166): the agent's OWN tool API keys (WEBFLOW_TOKEN, PERPLEXITY_API_KEY, …) were
151
135
  # being passed INLINE as KEY=VALUE on the env -i line → stored PLAINTEXT in tmux's pane_start_command
152
136
  # (`tmux list-panes` = any co-resident proc) and in claude's env (ps eww). Same inline-secret class as the
153
137
  # bearer/token. Stage the already-validated entries to a 0600 file and pass ONLY AR_TOOL_ENV_FILE (a PATH);
154
138
  # the wrapper reads+exports them just before exec, so the VALUES never appear on the pane command / argv
155
- # (still in claude's env by necessity, exactly like the OAuth token — acceptable, same-user only).
139
+ # (tool credentials remain in Claude's environment because their MCP subprocesses require them).
156
140
  _TOOL_ENV_FILE=""
157
141
  if [ "${#TOOL_ENV_ARGS[@]}" -gt 0 ]; then
158
142
  _TOOL_ENV_FILE="${_SECRETS_DIR}/tool_env"
@@ -221,31 +205,22 @@ ensure_agent() {
221
205
  fi
222
206
  # SCRUBBED launch: env -i with a strict allowlist so the agent inherits nothing from this daemon
223
207
  # (AWP_BEARER, AWP_MCP_URL, etc. never reach the pane). CONNECTOR ISOLATION is enforced by
224
- # --strict-mcp-config (the agent loads ONLY $MCP_CONFIG's servers) — NOT by the config dir, which is
225
- # deliberately the agent's EXISTING CLAUDE_CONFIG_DIR (often the shared ~/.claude) so `claude
226
- # --continue` resumes the real conversation. The agent's own tool creds (from AGENT_TOOL_ENV_FILE) are
227
- # 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
228
211
  # AR_TOOL_ENV_FILE, which the wrapper reads+exports before exec — so no tool VALUE appears on the env -i
229
212
  # argv / tmux pane_start_command. Reserved keys were already rejected at tool-file parse time (2b).
230
213
  log "starting agent in tmux '${TMUX_SESSION}' (flags: ${CLAUDE_EXTRA_FLAGS:-none}; tool-env file: ${_TOOL_ENV_FILE:-none})"
231
214
  # SC2086: CLAUDE_EXTRA_FLAGS is intentionally UNQUOTED so "--chrome" (etc.) word-splits into flags.
232
- # _tool_env / _oauth_env use the ${arr[@]+"${arr[@]}"} form so an EMPTY array is safe under `set -u` (bash 3.2).
215
+ # _tool_env uses the ${arr[@]+"${arr[@]}"} form so an EMPTY array is safe under `set -u` (bash 3.2).
233
216
  # First-launch handling (#153/H4 durable-feeder): `claude --continue` EXITS 1 when the cwd has NO prior
234
- # conversation (a fresh engagement folder / brand-new CLAUDE_CONFIG_DIR) → the pane dies → the supervisor
217
+ # conversation (for example, a fresh engagement folder) → the pane dies → the supervisor
235
218
  # crash-loops. So try `--continue`, and FALL BACK to a fresh `claude` (which stays up AND creates the
236
219
  # session) when there's nothing to resume. Self-healing: once a conversation exists, --continue resumes it;
237
220
  # if it's ever lost, we cleanly start fresh instead of exit-looping. (--continue exits 1 on no-session,
238
221
  # verified — so the `||` fallback is reliable.) The `/bin/sh -c` wrapper keeps EXTRA_FLAGS word-splitting.
239
222
  # shellcheck disable=SC2086
240
223
  _launch_flags=( ${CLAUDE_EXTRA_FLAGS} --strict-mcp-config --mcp-config="${MCP_CONFIG}" )
241
- # OAuth setup-token (#152): the token was already staged to a 0600 file + UNSET from this env in section
242
- # 2b (so it's off both the supervisor+feeder env / ps eww). Here we only pass AR_OAUTH_TOKEN_FILE (a PATH,
243
- # not the secret) on the env -i line — so the token is NOT in the `env`/claude argv (`ps aux`) either. The
244
- # wrapper below reads the file into CLAUDE_CODE_OAUTH_TOKEN just before exec'ing claude. When it was UNSET
245
- # (subscription-login mode) _OAUTH_TOKEN_FILE is empty → we pass NOTHING token-related and the wrapper's
246
- # read is a no-op → claude falls through to subscription OAuth exactly as before. bash 3.2-safe array form.
247
- _oauth_env=()
248
- [ -n "${_OAUTH_TOKEN_FILE}" ] && _oauth_env=( AR_OAUTH_TOKEN_FILE="${_OAUTH_TOKEN_FILE}" )
249
224
  # #166: pass the tool creds as a FILE PATH (AR_TOOL_ENV_FILE), never inline KEY=VALUE on env -i.
250
225
  _tool_env=()
251
226
  [ -n "${_TOOL_ENV_FILE}" ] && _tool_env=( AR_TOOL_ENV_FILE="${_TOOL_ENV_FILE}" )
@@ -277,8 +252,8 @@ ensure_agent() {
277
252
  LANG="${LANG:-en_US.UTF-8}" LC_ALL="${LC_ALL:-}" LC_CTYPE="${AR_AGENT_LC_CTYPE:-en_US.UTF-8}" \
278
253
  TMPDIR="${TMPDIR:-/tmp}" NODE_OPTIONS="${NODE_OPTIONS:-}" \
279
254
  DISABLE_AUTOUPDATER=1 \
280
- ${_tool_env[@]+"${_tool_env[@]}"} ${_oauth_env[@]+"${_oauth_env[@]}"} CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR}" \
281
- /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; if [ -n "${AR_OAUTH_TOKEN_FILE:-}" ]; then CLAUDE_CODE_OAUTH_TOKEN="$(cat "$AR_OAUTH_TOKEN_FILE")"; export CLAUDE_CODE_OAUTH_TOKEN; unset AR_OAUTH_TOKEN_FILE; fi; CB="$1"; shift; "$CB" --continue "$@" || exec "$CB" "$@"' sh "${CLAUDE_BIN}" "${_launch_flags[@]}" || {
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[@]}" || {
282
257
  log "tmux could not start agent session '${TMUX_SESSION}'"
283
258
  return 1
284
259
  }
@@ -293,7 +268,7 @@ FEEDER_PID=""
293
268
  cleanup() {
294
269
  [ -n "${FEEDER_PID}" ] && kill "${FEEDER_PID}" 2>/dev/null || true
295
270
  [ -n "${BRIDGE_PID:-}" ] && kill "${BRIDGE_PID}" 2>/dev/null || true # #133 terminal bridge
296
- # #152: remove the staged 0600 secret files (bearer + optional oauth token) on exit.
271
+ # #152: remove the staged 0600 secret files on exit.
297
272
  [ -n "${_SECRETS_DIR:-}" ] && rm -rf "${_SECRETS_DIR}" 2>/dev/null || true
298
273
  }
299
274
  trap cleanup EXIT INT TERM
@@ -307,8 +282,7 @@ log "starting feeder (${FEEDER_JS}) → session '${TMUX_SESSION}'"
307
282
  # #152: pass AWP_BEARER_FILE (a 0600 PATH), NEVER the raw AWP_BEARER — the feeder reads the file, so the
308
283
  # bearer is not in the feeder process's environment (`ps eww`) or argv. AWP_MCP_URL is not a secret.
309
284
  AWP_MCP_URL="${AWP_MCP_URL}" AWP_BEARER_FILE="${AWP_BEARER_FILE}" TMUX_SESSION="${TMUX_SESSION}" \
310
- AGENT_PID_PATTERN="${AGENT_PID_PATTERN}" CLAUDE_BIN="${CLAUDE_BIN}" CLAUDE_CONFIG_DIR="${CLAUDE_CONFIG_DIR}" \
311
- AR_OAUTH_TOKEN_FILE="${_OAUTH_TOKEN_FILE}" node "${FEEDER_JS}" &
285
+ AGENT_PID_PATTERN="${AGENT_PID_PATTERN}" CLAUDE_BIN="${CLAUDE_BIN}" node "${FEEDER_JS}" &
312
286
  FEEDER_PID=$!
313
287
  log "feeder pid ${FEEDER_PID}"
314
288
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentradar-cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Secure AgentRadar vendor setup and skill installer",
5
5
  "type": "module",
6
6
  "bin": {