agentradar-cli 0.1.0 → 0.1.1

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
@@ -9,7 +9,11 @@ npx -y agentradar-cli setup
9
9
  npx -y agentradar-cli skill install
10
10
  ```
11
11
 
12
- 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 MCP entry to the project-local section of `~/.claude.json` with mode `0600`. It never prints the bearer or puts it in a URL, command argument, environment variable, or repository file.
12
+ 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 MCP entry to the project-local section of `$CLAUDE_CONFIG_DIR/.claude.json`, or `~/.claude.json` when that variable is unset, with mode `0600`. It prints the path it wrote. It never prints the bearer or puts it in a URL, command argument, environment variable, or repository file.
13
+
14
+ 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.
15
+
16
+ There is a `--trust` flag for unattended runners, where a launch prompt would hang the process with nobody there to answer it. It is not part of normal onboarding and is deliberately not given here as a line to paste: read the paragraph above, decide the folder holds nothing you would not run, and then type it.
13
17
 
14
18
  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.
15
19
 
package/dist/index.js CHANGED
@@ -7,11 +7,15 @@ async function main() {
7
7
  await setupVendorAgent();
8
8
  return;
9
9
  }
10
+ if (args.length === 2 && args[0] === "setup" && args[1] === "--trust") {
11
+ await setupVendorAgent({ trust: true });
12
+ return;
13
+ }
10
14
  if (args.length === 2 && args[0] === "skill" && args[1] === "install") {
11
15
  await installVendorSkill();
12
16
  return;
13
17
  }
14
- console.error("Usage: agentradar setup | agentradar skill install");
18
+ console.error("Usage: agentradar setup [--trust] | agentradar skill install");
15
19
  process.exitCode = 1;
16
20
  }
17
21
  main().catch((error) => {
package/dist/setup.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { lstat, readFile, realpath, rename, chmod, writeFile } from "node:fs/promises";
2
- import { basename, join } from "node:path";
2
+ import { basename, join, resolve } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  const DEFAULT_API_URL = "https://vendor.agentradar.com/api";
5
5
  async function postJson(fetchImpl, url, body) {
@@ -14,21 +14,64 @@ async function responseJson(response) {
14
14
  throw new Error(`AgentRadar setup request failed (${response.status})`);
15
15
  return response.json();
16
16
  }
17
- async function installProjectMcp(home, cwd, installation) {
17
+ /**
18
+ * D3 — where Claude Code actually reads its config from.
19
+ *
20
+ * `CLAUDE_CONFIG_DIR` relocates `.claude.json`, so a vendor running under a scrubbed/managed
21
+ * config dir (every runner in `docs/vendor-agent-runtime.md`, the exam runner, the whole mini
22
+ * fleet) had its MCP written to a `~/.claude.json` that the agent never reads — setup reported
23
+ * success and the agent came up with no AgentRadar MCP at all.
24
+ *
25
+ * 🔴 The resolution is ASYMMETRIC and that is the trap. `resolveClaudeConfigDir()` in
26
+ * `packages/db/scripts/clean-poisoned-claude-sessions.ts:93` answers `~/.claude` when the var is
27
+ * unset, but the JSON file does NOT live inside that directory by default — it is `~/.claude.json`,
28
+ * a sibling. Appending `.claude.json` to that resolver would yield `~/.claude/.claude.json`, which
29
+ * Claude Code never opens, and would break the DEFAULT path while fixing the env one. Hence the
30
+ * explicit branch rather than reuse; only the env arm joins into the directory
31
+ * (`$CLAUDE_CONFIG_DIR/.claude.json`, per `docs/vendor-agent-runtime.md:67`).
32
+ *
33
+ * Exported for the regression test — the env-SET case is the one that matters.
34
+ */
35
+ export function claudeConfigPath(home, env = process.env) {
36
+ const fromEnv = env.CLAUDE_CONFIG_DIR;
37
+ if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
38
+ return join(resolve(fromEnv.trim()), ".claude.json");
39
+ }
40
+ return join(home, ".claude.json");
41
+ }
42
+ /**
43
+ * D4 — `hasTrustDialogAccepted` is OPT-IN, never inferred.
44
+ *
45
+ * Claude Code blocks at the trust dialog on first launch, which stalls unattended onboarding, so
46
+ * the obvious move is to pre-accept it here. We don't, because the flag trusts the whole DIRECTORY
47
+ * — a pre-existing `.claude/settings.json`, hook or skill executes on launch under it. The consent
48
+ * a vendor gives `agentradar setup` is "configure AgentRadar here"; the flag grants "execute
49
+ * whatever this folder says". Those differ most for the vendors least equipped to notice.
50
+ *
51
+ * 🔴 The obvious narrowing — "set it only for a folder our CLI created" — is NOT available, and
52
+ * checked rather than assumed: `index.ts` dispatches only `setup` and `skill install`; setup runs
53
+ * *in* an existing cwd and creates nothing, and the only `mkdir` in this package is
54
+ * `skill-install.ts:18`, which creates `.claude/skills/…` INSIDE an already-existing project. So
55
+ * there is no `mkdirSync(…, {recursive:true})` return value to read as provenance, and creating a
56
+ * directory purely to manufacture that signal would be inventing the evidence. Every setup run is
57
+ * the pre-existing case by construction; `--trust` is the only way the flag is ever set.
58
+ *
59
+ * Returns the resolved project path so the caller can name it in the trust guidance.
60
+ */
61
+ async function installProjectMcp(configPath, cwd, installation, trust) {
18
62
  const endpoint = new URL(installation.mcpEndpoint);
19
63
  if (endpoint.protocol !== "https:" || !/^ar_[A-Za-z0-9_-]+$/.test(installation.bearer)) {
20
64
  throw new Error("AgentRadar returned an invalid setup payload");
21
65
  }
22
66
  const projectPath = await realpath(cwd);
23
- const configPath = join(home, ".claude.json");
24
67
  let config = {};
25
68
  try {
26
69
  const info = await lstat(configPath);
27
70
  if (info.isSymbolicLink())
28
- throw new Error("Refusing to replace a symlinked ~/.claude.json");
71
+ throw new Error(`Refusing to replace a symlinked ${configPath}`);
29
72
  const parsed = JSON.parse(await readFile(configPath, "utf8"));
30
73
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31
- throw new Error("~/.claude.json must contain a JSON object");
74
+ throw new Error(`${configPath} must contain a JSON object`);
32
75
  }
33
76
  config = parsed;
34
77
  }
@@ -52,12 +95,18 @@ async function installProjectMcp(home, cwd, installation) {
52
95
  timeout: 1_800_000,
53
96
  };
54
97
  project.mcpServers = mcpServers;
98
+ // SET only, never clear. A later plain `setup` must not revoke a decision an earlier `--trust`
99
+ // run made — that would silently regress a re-onboarding vendor back into the launch prompt, and
100
+ // this file is the user's, not ours.
101
+ if (trust)
102
+ project.hasTrustDialogAccepted = true;
55
103
  projects[projectPath] = project;
56
104
  config.projects = projects;
57
105
  const tempPath = `${configPath}.${process.pid}.tmp`;
58
106
  await writeFile(tempPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
59
107
  await rename(tempPath, configPath);
60
108
  await chmod(configPath, 0o600);
109
+ return projectPath;
61
110
  }
62
111
  export async function setupVendorAgent(deps = {}) {
63
112
  const fetchImpl = deps.fetch ?? fetch;
@@ -88,8 +137,16 @@ export async function setupVendorAgent(deps = {}) {
88
137
  const approved = await responseJson(response);
89
138
  if (approved.status !== "approved")
90
139
  throw new Error("AgentRadar returned an invalid setup state");
91
- await installProjectMcp(home, cwd, approved.installation);
92
- log("AgentRadar MCP installed for this project. Start Claude Code and use /login with your claude.ai account.");
140
+ const configPath = claudeConfigPath(home);
141
+ const trust = deps.trust === true;
142
+ const projectPath = await installProjectMcp(configPath, cwd, approved.installation, trust);
143
+ // Print the path, not just "installed". D3 was a silent write to the wrong file under a
144
+ // relocated CLAUDE_CONFIG_DIR; naming the target turns that class of failure into a glance.
145
+ log(`AgentRadar MCP installed for this project → ${configPath}`);
146
+ log(trust
147
+ ? `--trust: pre-accepted the trust dialog for ${projectPath}. Claude Code will run this folder's existing hooks and settings without asking.`
148
+ : `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.`);
149
+ log("Start Claude Code and use /login with your claude.ai account.");
93
150
  return;
94
151
  }
95
152
  throw new Error("AgentRadar setup code expired; run setup again");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentradar-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Secure AgentRadar vendor setup and skill installer",
5
5
  "type": "module",
6
6
  "bin": {