agentradar-cli 0.1.2 → 0.1.4

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
@@ -6,10 +6,12 @@ From the agent's project folder:
6
6
 
7
7
  ```sh
8
8
  npx -y agentradar-cli setup
9
+ # Use the exact runner ID printed by setup:
10
+ npx -y agentradar-cli runner start <runner-id>
9
11
  npx -y agentradar-cli skill install
10
12
  ```
11
13
 
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.
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.
13
15
 
14
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.
15
17
 
@@ -17,4 +19,4 @@ There is a `--trust` flag for unattended runners, where a launch prompt would ha
17
19
 
18
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.
19
21
 
20
- 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`.
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.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { installVendorSkill } from "./skill-install.js";
3
+ import { startVendorRunner } from "./runner.js";
3
4
  import { setupVendorAgent } from "./setup.js";
4
5
  async function main() {
5
6
  const args = process.argv.slice(2);
@@ -11,11 +12,20 @@ async function main() {
11
12
  await setupVendorAgent({ trust: true });
12
13
  return;
13
14
  }
15
+ if (args.length === 2 && args[0] === "runner" && args[1] === "start") {
16
+ console.error("Usage: agentradar runner start <runner-id>");
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+ if (args.length === 3 && args[0] === "runner" && args[1] === "start") {
21
+ process.exitCode = await startVendorRunner(args[2]);
22
+ return;
23
+ }
14
24
  if (args.length === 2 && args[0] === "skill" && args[1] === "install") {
15
25
  await installVendorSkill();
16
26
  return;
17
27
  }
18
- console.error("Usage: agentradar setup [--trust] | agentradar skill install");
28
+ console.error("Usage: agentradar setup [--trust] | agentradar runner start <runner-id> | agentradar skill install");
19
29
  process.exitCode = 1;
20
30
  }
21
31
  main().catch((error) => {
package/dist/runner.js ADDED
@@ -0,0 +1,17 @@
1
+ import { spawn } from "node:child_process";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ export async function startVendorRunner(runnerId, home = homedir()) {
6
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(runnerId)) {
7
+ throw new Error("Invalid runner ID");
8
+ }
9
+ const envPath = join(home, ".agentradar", "agents", `${runnerId}.env`);
10
+ const supervisor = join(dirname(fileURLToPath(import.meta.url)), "vendor-runner", "supervisor", "supervise-agent.sh");
11
+ return new Promise((resolve, reject) => {
12
+ const child = spawn("bash", [supervisor, envPath], { stdio: "inherit", env: process.env });
13
+ child.once("error", reject);
14
+ child.once("exit", (code, signal) => resolve(code ?? (signal ? 1 : 0)));
15
+ });
16
+ }
17
+ //# sourceMappingURL=runner.js.map
package/dist/setup.js CHANGED
@@ -1,4 +1,4 @@
1
- import { lstat, readFile, realpath, rename, chmod, writeFile } from "node:fs/promises";
1
+ import { chmod, lstat, mkdir, readFile, realpath, rename, writeFile } from "node:fs/promises";
2
2
  import { basename, join, resolve } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import * as tls from "node:tls";
@@ -109,6 +109,58 @@ async function installProjectMcp(configPath, cwd, installation, trust) {
109
109
  await chmod(configPath, 0o600);
110
110
  return projectPath;
111
111
  }
112
+ function shellValue(value) {
113
+ return `'${value.replaceAll("'", "'\\''")}'`;
114
+ }
115
+ async function writePrivateFile(path, contents) {
116
+ try {
117
+ if ((await lstat(path)).isSymbolicLink())
118
+ throw new Error(`Refusing to replace a symlinked ${path}`);
119
+ }
120
+ catch (error) {
121
+ if (error.code !== "ENOENT")
122
+ throw error;
123
+ }
124
+ const tempPath = `${path}.${process.pid}.tmp`;
125
+ await writeFile(tempPath, contents, { mode: 0o600 });
126
+ await rename(tempPath, path);
127
+ await chmod(path, 0o600);
128
+ }
129
+ async function installRunner(home, projectPath, approved) {
130
+ if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(approved.runnerId)) {
131
+ throw new Error("AgentRadar returned an invalid runner ID");
132
+ }
133
+ const endpoint = new URL(approved.installation.mcpEndpoint);
134
+ if (endpoint.protocol !== "https:" || !/^ar_[A-Za-z0-9_-]+$/.test(approved.installation.bearer)) {
135
+ throw new Error("AgentRadar returned an invalid setup payload");
136
+ }
137
+ const agentDir = join(home, ".agentradar", "agents");
138
+ const configDir = join(home, ".agentradar", "config", approved.runnerId);
139
+ await mkdir(agentDir, { recursive: true, mode: 0o700 });
140
+ await mkdir(configDir, { recursive: true, mode: 0o700 });
141
+ await chmod(agentDir, 0o700);
142
+ await chmod(configDir, 0o700);
143
+ const mcpConfig = join(configDir, "awp-only.json");
144
+ const envPath = join(agentDir, `${approved.runnerId}.env`);
145
+ await writePrivateFile(mcpConfig, `${JSON.stringify({
146
+ mcpServers: {
147
+ agentradar: {
148
+ type: "http",
149
+ url: endpoint.toString(),
150
+ headers: { Authorization: `Bearer ${approved.installation.bearer}` },
151
+ },
152
+ },
153
+ }, null, 2)}\n`);
154
+ await writePrivateFile(envPath, [
155
+ `AGENT_NAME=${shellValue(approved.runnerId)}`,
156
+ `AR_BASE=${shellValue(projectPath)}`,
157
+ `AWP_MCP_URL=${shellValue(endpoint.toString())}`,
158
+ `AWP_BEARER=${shellValue(approved.installation.bearer)}`,
159
+ `MCP_CONFIG=${shellValue(mcpConfig)}`,
160
+ "",
161
+ ].join("\n"));
162
+ return envPath;
163
+ }
112
164
  export async function setupVendorAgent(deps = {}) {
113
165
  if (!deps.fetch && tls.getCACertificates && tls.setDefaultCACertificates) {
114
166
  tls.setDefaultCACertificates([...tls.getCACertificates("default"), ...tls.getCACertificates("system")]);
@@ -144,13 +196,16 @@ export async function setupVendorAgent(deps = {}) {
144
196
  const configPath = claudeConfigPath(home);
145
197
  const trust = deps.trust === true;
146
198
  const projectPath = await installProjectMcp(configPath, cwd, approved.installation, trust);
199
+ const runnerEnv = await installRunner(home, projectPath, approved);
147
200
  // Print the path, not just "installed". D3 was a silent write to the wrong file under a
148
201
  // relocated CLAUDE_CONFIG_DIR; naming the target turns that class of failure into a glance.
149
202
  log(`AgentRadar MCP installed for this project → ${configPath}`);
150
203
  log(trust
151
204
  ? `--trust: pre-accepted the trust dialog for ${projectPath}. Claude Code will run this folder's existing hooks and settings without asking.`
152
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.`);
153
- log("Start Claude Code and use /login with your claude.ai account.");
206
+ log(`Runner installed → ${runnerEnv}`);
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.");
154
209
  return;
155
210
  }
156
211
  throw new Error("AgentRadar setup code expired; run setup again");