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 +5 -1
- package/dist/index.js +5 -1
- package/dist/setup.js +68 -7
- package/package.json +1 -1
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
92
|
-
|
|
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");
|