agentradar-cli 0.2.5 → 0.2.7
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/dist/index.js +3 -3
- package/dist/setup.js +7 -12
- package/dist/skill-install.js +3 -70
- package/dist/tool-run.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export const runArgs = (raw) => raw[0] !== "run" ? raw
|
|
|
15
15
|
async function main() {
|
|
16
16
|
const args = runArgs(process.argv.slice(2));
|
|
17
17
|
if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
|
|
18
|
-
console.log("Usage: agentradar run <
|
|
18
|
+
console.log("Usage: agentradar run <join-link> [--name <n>] [--model <m>] | agentradar run <runner-id> <tool> [--<argument> <value> | --<argument>-file <path> ...] | agentradar setup [--trust] [--harness claude|codex] | agentradar join <join-link> [--name <n>] [--model <m>] [--trust] | agentradar leave <runner-id> | agentradar channel <runner-id> | agentradar selftest <runner-id> [--status|--restart] | agentradar runner start <runner-id> | agentradar tool list <runner-id> | agentradar tool run <runner-id> <tool-key> [--args '<json>'] | agentradar call <runner-id> <tool> [--<argument> <value> ...] [--args '<json>'] | agentradar task create|repeat <runner-id> --args '<json>' | agentradar skill install | agentradar sync [dir] | agentradar sync install [dir] [--interval <s>]");
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
if (args[0] === "leave" && args.length === 2) {
|
|
@@ -65,7 +65,7 @@ async function main() {
|
|
|
65
65
|
urls.push(args[i]);
|
|
66
66
|
}
|
|
67
67
|
if (urls.length !== 1 || [urls[0], ...Object.values(named)].some((value) => value.startsWith("-"))) {
|
|
68
|
-
console.error("Usage: agentradar join <
|
|
68
|
+
console.error("Usage: agentradar join <join-link> [--name <your name>] [--model <your model id>] [--trust]");
|
|
69
69
|
process.exitCode = 1;
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
@@ -129,7 +129,7 @@ async function main() {
|
|
|
129
129
|
return;
|
|
130
130
|
}
|
|
131
131
|
const help = args.length === 1 && (args[0] === "--help" || args[0] === "-h");
|
|
132
|
-
(help ? console.log : console.error)("Usage: agentradar run <
|
|
132
|
+
(help ? console.log : console.error)("Usage: agentradar run <join-link> [--name <n>] [--model <m>] | agentradar run <runner-id> <tool> [--<argument> <value> | --<argument>-file <path> ...] | agentradar setup [--trust] | agentradar join <join-link> [--name <n>] [--model <m>] [--trust] | agentradar leave <runner-id> | agentradar channel <runner-id> | agentradar selftest <runner-id> [--status|--restart] | agentradar runner start <runner-id> | agentradar tool list <runner-id> | agentradar tool run <runner-id> <tool-key> [--args '<json>'] | agentradar call <runner-id> <tool> [--<argument> <value> ...] [--args '<json>'] | agentradar task create|repeat <runner-id> --args '<json>' | agentradar skill install | agentradar sync [dir] | agentradar sync install [dir] [--interval <s>]");
|
|
133
133
|
if (!help)
|
|
134
134
|
process.exitCode = 1;
|
|
135
135
|
}
|
package/dist/setup.js
CHANGED
|
@@ -7,7 +7,6 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import * as tls from "node:tls";
|
|
9
9
|
import { promisify } from "node:util";
|
|
10
|
-
import { syncJoinedSkills } from "./skill-install.js";
|
|
11
10
|
const DEFAULT_API_URL = "https://vendor.agentradar.com/api";
|
|
12
11
|
/** The service's CLI build (the CLI itself and the agent-guard plugin): `installCli` keeps it current, never the npx cache. */
|
|
13
12
|
const cliDist = (home) => join(home, ".agentradar", "cli", "current", "dist");
|
|
@@ -556,7 +555,7 @@ async function installAgentService(runnerId, home, env) {
|
|
|
556
555
|
});
|
|
557
556
|
}
|
|
558
557
|
/**
|
|
559
|
-
* Mode A — the organisation's own agent, on its own compute, joining through
|
|
558
|
+
* Mode A — the organisation's own agent, on its own compute, joining through a join link.
|
|
560
559
|
*
|
|
561
560
|
* accept → wait for a human to approve → claim the key once → install the runner and start it as a service
|
|
562
561
|
* (LaunchAgent / systemd user unit) that owns the agent's persistent session. The one-time claim secret is
|
|
@@ -605,10 +604,6 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
605
604
|
const runnerEnv = await installRunner(home, projectPath, { status: "approved", agentId, runnerId, installation, harness }, { sessionId, env });
|
|
606
605
|
await saved?.();
|
|
607
606
|
log(`Runner installed → ${runnerEnv}`);
|
|
608
|
-
// Best effort: a failed first sync never stops the join; the service's feeder syncs them again.
|
|
609
|
-
await syncJoinedSkills(runnerId, { home, fetch: fetchImpl, log }).catch((error) => {
|
|
610
|
-
log(`Your AgentRadar skills are not installed yet (${String(error?.message ?? error).split("\n")[0]}); the runner installs them once it is running.`);
|
|
611
|
-
});
|
|
612
607
|
try {
|
|
613
608
|
await (deps.installService ?? ((id) => installAgentService(id, home, env)))(runnerId);
|
|
614
609
|
}
|
|
@@ -625,11 +620,11 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
625
620
|
link = new URL(enrolmentUrl);
|
|
626
621
|
}
|
|
627
622
|
catch {
|
|
628
|
-
throw new Error("Pass the
|
|
623
|
+
throw new Error("Pass the join link your organisation gave you, e.g. https://…/invite/<token>");
|
|
629
624
|
}
|
|
630
625
|
const token = /^\/(?:api\/invites|invite)\/([^/]+?)(?:\.md)?(?:\/|$)/.exec(link.pathname)?.[1];
|
|
631
626
|
if (!token || !trustedMcpEndpoint(link.origin)) {
|
|
632
|
-
throw new Error("That is not an AgentRadar
|
|
627
|
+
throw new Error("That is not an AgentRadar join link (expected https://…/invite/<token>)");
|
|
633
628
|
}
|
|
634
629
|
const api = (path) => new URL(`/api/${path}`, link.origin);
|
|
635
630
|
const mcpEndpoint = env.AWP_MCP_URL || api("mcp").toString();
|
|
@@ -645,12 +640,12 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
645
640
|
throw error;
|
|
646
641
|
}
|
|
647
642
|
if (state?.agentId) {
|
|
648
|
-
log("This
|
|
643
|
+
log("This machine already joined with this join link.");
|
|
649
644
|
const saved = await readFile(join(home, ".agentradar", "agents", `join-${state.requestId}.env`), "utf8").catch(() => "");
|
|
650
645
|
const { envValue } = await import("./tool-run.js");
|
|
651
646
|
const bearer = envValue(saved, "AWP_BEARER");
|
|
652
647
|
if (!bearer)
|
|
653
|
-
throw new Error("This machine left that organisation (agentradar leave). A key is issued once; ask your organisation for a new
|
|
648
|
+
throw new Error("This machine left that organisation (agentradar leave). A key is issued once; ask your organisation for a new join link.");
|
|
654
649
|
return startJoined(state.requestId, state.agentId, { mcpEndpoint: envValue(saved, "AWP_MCP_URL") ?? mcpEndpoint, bearer });
|
|
655
650
|
}
|
|
656
651
|
if (!state) {
|
|
@@ -712,10 +707,10 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
|
|
|
712
707
|
continue;
|
|
713
708
|
}
|
|
714
709
|
if (response.status === 409 && said.includes("rejected")) {
|
|
715
|
-
throw new Error("Your organisation declined this join request. Ask it for a new
|
|
710
|
+
throw new Error("Your organisation declined this join request. Ask it for a new join link.");
|
|
716
711
|
}
|
|
717
712
|
if (response.status === 409 && /already (?:used|claimed)/.test(said)) {
|
|
718
|
-
throw new Error("This
|
|
713
|
+
throw new Error("This join link's key was already claimed. A key is issued once and cannot be claimed again; ask your organisation for a new join link.");
|
|
719
714
|
}
|
|
720
715
|
throw new Error(said);
|
|
721
716
|
}
|
package/dist/skill-install.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { chmod, lstat, mkdir, readFile,
|
|
1
|
+
import { chmod, lstat, mkdir, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import {
|
|
4
|
-
import { awpRequest, envValue } from "./tool-run.js";
|
|
5
|
-
const MANAGED_MARK = ".agentradar-managed";
|
|
6
|
-
const SKILL_NAME = /^[a-z0-9][a-z0-9-]{0,99}$/;
|
|
3
|
+
import { join } from "node:path";
|
|
7
4
|
async function ensureDirectory(path) {
|
|
8
5
|
try {
|
|
9
6
|
const info = await lstat(path);
|
|
@@ -41,71 +38,7 @@ export async function installVendorSkill(deps = {}) {
|
|
|
41
38
|
});
|
|
42
39
|
}
|
|
43
40
|
// The runner keeps this skill current from AgentRadar; the marker says the folder is AgentRadar's to replace.
|
|
44
|
-
await writeFile(join(skillDir,
|
|
41
|
+
await writeFile(join(skillDir, ".agentradar-managed"), "Written by AgentRadar; replaced on every sync.\n");
|
|
45
42
|
(deps.log ?? console.log)("AgentRadar vendor skill installed for this project.");
|
|
46
43
|
}
|
|
47
|
-
const isManaged = async (dir) => (await lstat(dir).then((info) => info.isDirectory(), () => false)) && (await lstat(join(dir, MANAGED_MARK)).then(() => true, () => false));
|
|
48
|
-
/**
|
|
49
|
-
* The feeder's `writeManagedSkills` (templates/vendor-runner/feeder.mjs), for the CLI: the server's answer is
|
|
50
|
-
* untrusted input to this box, so names and paths are checked, each skill is staged then renamed into place,
|
|
51
|
-
* a folder without the marker (the vendor's own) is never touched, and a managed skill no longer listed goes.
|
|
52
|
-
*/
|
|
53
|
-
export async function writeManagedSkills(skillsDir, skills) {
|
|
54
|
-
await mkdir(skillsDir, { recursive: true });
|
|
55
|
-
const written = new Set();
|
|
56
|
-
for (const skill of skills) {
|
|
57
|
-
const name = String(skill?.name ?? "");
|
|
58
|
-
const target = join(skillsDir, name);
|
|
59
|
-
if (!SKILL_NAME.test(name))
|
|
60
|
-
continue;
|
|
61
|
-
if (await lstat(target).then(() => true, () => false) && !(await isManaged(target)))
|
|
62
|
-
continue;
|
|
63
|
-
const files = (Array.isArray(skill.files) ? skill.files : []);
|
|
64
|
-
if (files.some((f) => typeof f?.path !== "string" || typeof f?.content !== "string" || isAbsolute(f.path)
|
|
65
|
-
|| normalize(f.path).split(sep).includes("..") || [".", MANAGED_MARK].includes(normalize(f.path))))
|
|
66
|
-
continue;
|
|
67
|
-
const staging = join(skillsDir, `.${name}.sync-${process.pid}`);
|
|
68
|
-
try {
|
|
69
|
-
await rm(staging, { recursive: true, force: true });
|
|
70
|
-
for (const f of files) {
|
|
71
|
-
await mkdir(dirname(join(staging, f.path)), { recursive: true });
|
|
72
|
-
await writeFile(join(staging, f.path), f.content);
|
|
73
|
-
}
|
|
74
|
-
await writeFile(join(staging, MANAGED_MARK), "Written by the AgentRadar runner; replaced on every sync.\n");
|
|
75
|
-
await rm(target, { recursive: true, force: true });
|
|
76
|
-
await rename(staging, target);
|
|
77
|
-
written.add(name);
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
await rm(staging, { recursive: true, force: true });
|
|
81
|
-
if (await isManaged(target))
|
|
82
|
-
written.add(name);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
for (const entry of await readdir(skillsDir)) {
|
|
86
|
-
if (!written.has(entry) && await isManaged(join(skillsDir, entry)))
|
|
87
|
-
await rm(join(skillsDir, entry), { recursive: true, force: true });
|
|
88
|
-
}
|
|
89
|
-
return [...written];
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* R650 — a joined agent's AgentRadar skills (`my_skills`: the bundled set plus the ones linked to it), written
|
|
93
|
-
* at join into the directory its CLI reads, the feeder's `skillsDirFor`: Claude `$AR_BASE/.claude/skills`,
|
|
94
|
-
* Codex `$CODEX_HOME/skills`. Done here because a session that acts only through `agentradar run` has no
|
|
95
|
-
* feeder; where the service runs, its feeder keeps them current.
|
|
96
|
-
*/
|
|
97
|
-
export async function syncJoinedSkills(runnerId, deps) {
|
|
98
|
-
const env = await readFile(join(deps.home, ".agentradar", "agents", `${runnerId}.env`), "utf8");
|
|
99
|
-
const root = envValue(env, "AR_AGENT_HARNESS") === "codex" ? envValue(env, "CODEX_HOME") : envValue(env, "AR_BASE");
|
|
100
|
-
if (!root)
|
|
101
|
-
return;
|
|
102
|
-
const skillsDir = envValue(env, "AR_AGENT_HARNESS") === "codex" ? join(root, "skills") : join(root, ".claude", "skills");
|
|
103
|
-
const body = await awpRequest(runnerId, "tools/call", { name: "my_skills", arguments: {} }, { home: deps.home, fetch: deps.fetch, logError: deps.log });
|
|
104
|
-
const text = body?.result?.content?.find((c) => c.type === "text")?.text;
|
|
105
|
-
const answer = (text ? JSON.parse(text) : {});
|
|
106
|
-
if (!answer.ok || !Array.isArray(answer.skills))
|
|
107
|
-
throw new Error("AgentRadar did not return your skills");
|
|
108
|
-
const written = await writeManagedSkills(skillsDir, answer.skills);
|
|
109
|
-
deps.log(`Installed ${written.length} AgentRadar skill(s) in ${skillsDir}.`);
|
|
110
|
-
}
|
|
111
44
|
//# sourceMappingURL=skill-install.js.map
|
package/dist/tool-run.js
CHANGED
|
@@ -203,7 +203,7 @@ export async function callAwpTool(runnerId, name, toolArguments, deps) {
|
|
|
203
203
|
}
|
|
204
204
|
const RUNNER_ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
205
205
|
/** One JSON-RPC request on the runner's AWP MCP endpoint with the runner's own key; null after logging why it failed. */
|
|
206
|
-
|
|
206
|
+
async function awpRequest(runnerId, method, params, deps) {
|
|
207
207
|
const logError = deps.logError ?? console.error;
|
|
208
208
|
if (!RUNNER_ID.test(runnerId)) {
|
|
209
209
|
logError("Invalid runner ID");
|