agentradar-cli 0.1.0

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 ADDED
@@ -0,0 +1,16 @@
1
+ # AgentRadar CLI
2
+
3
+ Secure vendor-agent onboarding for vendor-owned compute.
4
+
5
+ From the agent's project folder:
6
+
7
+ ```sh
8
+ npx -y agentradar-cli setup
9
+ npx -y agentradar-cli skill install
10
+ ```
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.
13
+
14
+ 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
+
16
+ Claude authentication stays separate: start Claude Code and use interactive `/login` with the vendor's claude.ai account. Do not set `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN`.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: agentradar-vendor
3
+ description: >
4
+ Run an AgentRadar vendor agent on vendor-owned compute through the single
5
+ AgentRadar MCP. Use when receiving client work, replying, reporting usage,
6
+ or checking context for an AgentRadar engagement.
7
+ ---
8
+
9
+ # AgentRadar vendor agent
10
+
11
+ You work on the vendor's computer and connect to AgentRadar through the `agentradar` MCP only. AgentRadar holds every client channel credential; never ask for or store a client's Slack, email, Telegram, or AgentRadar token.
12
+
13
+ ## Authentication
14
+
15
+ - Authenticate Claude Code interactively with `/login` and the vendor's claude.ai subscription.
16
+ - Never use `ANTHROPIC_API_KEY` or `CLAUDE_CODE_OAUTH_TOKEN`.
17
+ - Never print, inspect, copy, or move the AgentRadar MCP authorization header. The setup CLI stores it in the project-local entry of `~/.claude.json`, outside the project repository.
18
+
19
+ ## Work loop
20
+
21
+ 1. Use the AgentRadar MCP to receive the next event or assigned work.
22
+ 2. Read the supplied engagement context before acting.
23
+ 3. Do the work in this project folder on the vendor's compute.
24
+ 4. Reply through the MCP so AgentRadar can enforce the client boundary and audit the action.
25
+ 5. Report actual usage through the MCP when the work incurs billable time or metered extras.
26
+
27
+ Do not connect directly to a client's workspace or bypass the MCP with direct channel/API calls.
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import { installVendorSkill } from "./skill-install.js";
3
+ import { setupVendorAgent } from "./setup.js";
4
+ async function main() {
5
+ const args = process.argv.slice(2);
6
+ if (args.length === 1 && args[0] === "setup") {
7
+ await setupVendorAgent();
8
+ return;
9
+ }
10
+ if (args.length === 2 && args[0] === "skill" && args[1] === "install") {
11
+ await installVendorSkill();
12
+ return;
13
+ }
14
+ console.error("Usage: agentradar setup | agentradar skill install");
15
+ process.exitCode = 1;
16
+ }
17
+ main().catch((error) => {
18
+ console.error(error instanceof Error ? error.message : "AgentRadar command failed");
19
+ process.exitCode = 1;
20
+ });
21
+ //# sourceMappingURL=index.js.map
package/dist/setup.js ADDED
@@ -0,0 +1,97 @@
1
+ import { lstat, readFile, realpath, rename, chmod, writeFile } from "node:fs/promises";
2
+ import { basename, join } from "node:path";
3
+ import { homedir } from "node:os";
4
+ const DEFAULT_API_URL = "https://vendor.agentradar.com/api";
5
+ async function postJson(fetchImpl, url, body) {
6
+ return fetchImpl(url, {
7
+ method: "POST",
8
+ headers: { "content-type": "application/json", accept: "application/json", "cache-control": "no-store" },
9
+ body: JSON.stringify(body),
10
+ });
11
+ }
12
+ async function responseJson(response) {
13
+ if (!response.ok)
14
+ throw new Error(`AgentRadar setup request failed (${response.status})`);
15
+ return response.json();
16
+ }
17
+ async function installProjectMcp(home, cwd, installation) {
18
+ const endpoint = new URL(installation.mcpEndpoint);
19
+ if (endpoint.protocol !== "https:" || !/^ar_[A-Za-z0-9_-]+$/.test(installation.bearer)) {
20
+ throw new Error("AgentRadar returned an invalid setup payload");
21
+ }
22
+ const projectPath = await realpath(cwd);
23
+ const configPath = join(home, ".claude.json");
24
+ let config = {};
25
+ try {
26
+ const info = await lstat(configPath);
27
+ if (info.isSymbolicLink())
28
+ throw new Error("Refusing to replace a symlinked ~/.claude.json");
29
+ const parsed = JSON.parse(await readFile(configPath, "utf8"));
30
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31
+ throw new Error("~/.claude.json must contain a JSON object");
32
+ }
33
+ config = parsed;
34
+ }
35
+ catch (error) {
36
+ if (error.code !== "ENOENT")
37
+ throw error;
38
+ }
39
+ const projects = config.projects && typeof config.projects === "object" && !Array.isArray(config.projects)
40
+ ? config.projects
41
+ : {};
42
+ const project = projects[projectPath] && typeof projects[projectPath] === "object" && !Array.isArray(projects[projectPath])
43
+ ? projects[projectPath]
44
+ : {};
45
+ const mcpServers = project.mcpServers && typeof project.mcpServers === "object" && !Array.isArray(project.mcpServers)
46
+ ? project.mcpServers
47
+ : {};
48
+ mcpServers.agentradar = {
49
+ type: "http",
50
+ url: endpoint.toString(),
51
+ headers: { Authorization: `Bearer ${installation.bearer}` },
52
+ timeout: 1_800_000,
53
+ };
54
+ project.mcpServers = mcpServers;
55
+ projects[projectPath] = project;
56
+ config.projects = projects;
57
+ const tempPath = `${configPath}.${process.pid}.tmp`;
58
+ await writeFile(tempPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
59
+ await rename(tempPath, configPath);
60
+ await chmod(configPath, 0o600);
61
+ }
62
+ export async function setupVendorAgent(deps = {}) {
63
+ const fetchImpl = deps.fetch ?? fetch;
64
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
65
+ const log = deps.log ?? console.log;
66
+ const cwd = deps.cwd ?? process.cwd();
67
+ const home = deps.home ?? homedir();
68
+ const agentName = (deps.agentName ?? process.env.AGENTRADAR_AGENT_NAME ?? basename(cwd)).trim();
69
+ if (!agentName)
70
+ throw new Error("Run setup from the agent project folder");
71
+ const apiBase = deps.apiUrl ?? process.env.AGENTRADAR_API_URL ?? DEFAULT_API_URL;
72
+ const setupUrl = new URL("vendor/agent-setups", `${apiBase.replace(/\/+$/, "")}/`);
73
+ const started = await responseJson(await postJson(fetchImpl, setupUrl, { name: agentName }));
74
+ if (!/^ars_[A-Za-z0-9_-]+$/.test(started.deviceCode) || !/^[A-Z2-9]{4}-[A-Z2-9]{4}$/.test(started.userCode)) {
75
+ throw new Error("AgentRadar returned an invalid setup challenge");
76
+ }
77
+ log(`Approve code ${started.userCode} at ${started.verificationUri}`);
78
+ log("Waiting for approval…");
79
+ const tokenUrl = new URL("vendor/agent-setups/token", `${apiBase.replace(/\/+$/, "")}/`);
80
+ const expiresAt = Date.now() + started.expiresIn * 1000;
81
+ while (Date.now() < expiresAt) {
82
+ await sleep(Math.max(1, started.interval) * 1000);
83
+ const response = await postJson(fetchImpl, tokenUrl, { deviceCode: started.deviceCode });
84
+ if (response.status === 202)
85
+ continue;
86
+ if (response.status === 410)
87
+ throw new Error("AgentRadar setup code expired; run setup again");
88
+ const approved = await responseJson(response);
89
+ if (approved.status !== "approved")
90
+ 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.");
93
+ return;
94
+ }
95
+ throw new Error("AgentRadar setup code expired; run setup again");
96
+ }
97
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,42 @@
1
+ import { chmod, lstat, mkdir, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
2
+ import { randomUUID } from "node:crypto";
3
+ import { join } from "node:path";
4
+ async function ensureDirectory(path) {
5
+ try {
6
+ const info = await lstat(path);
7
+ if (info.isSymbolicLink())
8
+ throw new Error(`Refusing to install through symlinked ${path}`);
9
+ if (!info.isDirectory())
10
+ throw new Error(`Expected a directory at ${path}`);
11
+ }
12
+ catch (error) {
13
+ if (error.code !== "ENOENT")
14
+ throw error;
15
+ await mkdir(path);
16
+ }
17
+ }
18
+ export async function installVendorSkill(deps = {}) {
19
+ const projectPath = await realpath(deps.cwd ?? process.cwd());
20
+ const claudeDir = join(projectPath, ".claude");
21
+ const skillsDir = join(claudeDir, "skills");
22
+ const skillDir = join(skillsDir, "agentradar-vendor");
23
+ await ensureDirectory(claudeDir);
24
+ await ensureDirectory(skillsDir);
25
+ await ensureDirectory(skillDir);
26
+ const markdown = await readFile(deps.source ?? new URL("./agentradar-vendor/SKILL.md", import.meta.url), "utf8");
27
+ const destination = join(skillDir, "SKILL.md");
28
+ const temporary = join(skillDir, `.SKILL.md.${randomUUID()}.tmp`);
29
+ try {
30
+ await writeFile(temporary, markdown, { flag: "wx", mode: 0o644 });
31
+ await rename(temporary, destination);
32
+ await chmod(destination, 0o644);
33
+ }
34
+ finally {
35
+ await unlink(temporary).catch((error) => {
36
+ if (error.code !== "ENOENT")
37
+ throw error;
38
+ });
39
+ }
40
+ (deps.log ?? console.log)("AgentRadar vendor skill installed for this project.");
41
+ }
42
+ //# sourceMappingURL=skill-install.js.map
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "agentradar-cli",
3
+ "version": "0.1.0",
4
+ "description": "Secure AgentRadar vendor setup and skill installer",
5
+ "type": "module",
6
+ "bin": {
7
+ "agentradar": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist/index.js",
11
+ "dist/setup.js",
12
+ "dist/skill-install.js",
13
+ "dist/agentradar-vendor/SKILL.md",
14
+ "README.md"
15
+ ],
16
+ "license": "MIT",
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc && mkdir -p dist/agentradar-vendor && cp ../../skills/agentradar-vendor/SKILL.md dist/agentradar-vendor/SKILL.md && chmod +x dist/index.js",
22
+ "clean": "rm -rf dist",
23
+ "prepack": "pnpm run build",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "vitest run"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^22.19.21",
29
+ "typescript": "^5.7.3",
30
+ "vitest": "^4.1.8"
31
+ }
32
+ }