agentradar-cli 0.1.0 → 0.1.2

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