agentradar-cli 0.2.6 → 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 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 <enrolment-url> [--name <n>] [--model <m>] | agentradar run <runner-id> <tool> [--<argument> <value> | --<argument>-file <path> ...] | agentradar setup [--trust] [--harness claude|codex] | agentradar join <enrolment-url> [--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>]");
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 <enrolment-url> [--name <your name>] [--model <your model id>] [--trust]");
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 <enrolment-url> [--name <n>] [--model <m>] | agentradar run <runner-id> <tool> [--<argument> <value> | --<argument>-file <path> ...] | agentradar setup [--trust] | agentradar join <enrolment-url> [--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>]");
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 an enrolment link.
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 enrolment link your organisation gave you, e.g. https://…/invite/<token>");
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 enrolment link (expected https://…/invite/<token>)");
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 enrolment link is already joined on this machine.");
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 enrolment link.");
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 enrolment link.");
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 enrolment link's key was already claimed. A key is issued once and cannot be claimed again; ask your organisation for a new enrolment link.");
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
  }
@@ -1,9 +1,6 @@
1
- import { chmod, lstat, mkdir, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
1
+ import { chmod, lstat, mkdir, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
2
2
  import { randomUUID } from "node:crypto";
3
- import { dirname, isAbsolute, join, normalize, sep } from "node:path";
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,98 +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, MANAGED_MARK), "Written by AgentRadar; replaced on every sync.\n");
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
- const sameSkill = async (dir, files) => {
54
- try {
55
- const have = [];
56
- for (const p of await readdir(dir, { recursive: true }))
57
- if (p !== MANAGED_MARK && (await stat(join(dir, p))).isFile())
58
- have.push(p);
59
- for (const f of files)
60
- if (await readFile(join(dir, f.path), "utf8") !== f.content)
61
- return false;
62
- return have.length === files.length;
63
- }
64
- catch {
65
- return false;
66
- }
67
- };
68
- export async function writeManagedSkills(skillsDir, skills) {
69
- await mkdir(skillsDir, { recursive: true });
70
- const written = new Set();
71
- for (const skill of skills) {
72
- const name = String(skill?.name ?? "");
73
- const target = join(skillsDir, name);
74
- if (!SKILL_NAME.test(name))
75
- continue;
76
- if (await lstat(target).then(() => true, () => false) && !(await isManaged(target)))
77
- continue;
78
- const files = (Array.isArray(skill.files) ? skill.files : []);
79
- if (files.some((f) => typeof f?.path !== "string" || typeof f?.content !== "string" || isAbsolute(f.path)
80
- || normalize(f.path).split(sep).includes("..") || [".", MANAGED_MARK].includes(normalize(f.path))))
81
- continue;
82
- // An unchanged skill is not rewritten: the agent's CLI may be reading it.
83
- if (await sameSkill(target, files)) {
84
- written.add(name);
85
- continue;
86
- }
87
- const staging = join(skillsDir, `.${name}.sync-${process.pid}`);
88
- const old = join(skillsDir, `.${name}.old-${process.pid}`);
89
- try {
90
- await rm(staging, { recursive: true, force: true });
91
- for (const f of files) {
92
- await mkdir(dirname(join(staging, f.path)), { recursive: true });
93
- await writeFile(join(staging, f.path), f.content);
94
- }
95
- await writeFile(join(staging, MANAGED_MARK), "Written by the AgentRadar runner; replaced on every sync.\n");
96
- // Rename aside, then into place: the skill is missing for two renames, not for a recursive delete.
97
- await rm(old, { recursive: true, force: true });
98
- await rename(target, old).catch((err) => { if (err?.code !== "ENOENT")
99
- throw err; });
100
- await rename(staging, target);
101
- await rm(old, { recursive: true, force: true });
102
- written.add(name);
103
- }
104
- catch {
105
- await rm(staging, { recursive: true, force: true });
106
- if (!(await lstat(target).then(() => true, () => false)))
107
- await rename(old, target).catch(() => { });
108
- if (await isManaged(target))
109
- written.add(name);
110
- }
111
- }
112
- for (const entry of await readdir(skillsDir)) {
113
- if (!written.has(entry) && await isManaged(join(skillsDir, entry)))
114
- await rm(join(skillsDir, entry), { recursive: true, force: true });
115
- }
116
- return [...written];
117
- }
118
- /**
119
- * R650 — a joined agent's AgentRadar skills (`my_skills`: the bundled set plus the ones linked to it), written
120
- * at join into the directory its CLI reads, the feeder's `skillsDirFor`: Claude `$AR_BASE/.claude/skills`,
121
- * Codex `$CODEX_HOME/skills`. Done here because a session that acts only through `agentradar run` has no
122
- * feeder; where the service runs, its feeder keeps them current.
123
- */
124
- export async function syncJoinedSkills(runnerId, deps) {
125
- const env = await readFile(join(deps.home, ".agentradar", "agents", `${runnerId}.env`), "utf8");
126
- const root = envValue(env, "AR_AGENT_HARNESS") === "codex" ? envValue(env, "CODEX_HOME") : envValue(env, "AR_BASE");
127
- if (!root)
128
- return;
129
- const skillsDir = envValue(env, "AR_AGENT_HARNESS") === "codex" ? join(root, "skills") : join(root, ".claude", "skills");
130
- const body = await awpRequest(runnerId, "tools/call", { name: "my_skills", arguments: {} }, { home: deps.home, fetch: deps.fetch, logError: deps.log });
131
- const text = body?.result?.content?.find((c) => c.type === "text")?.text;
132
- const answer = (text ? JSON.parse(text) : {});
133
- if (!answer.ok || !Array.isArray(answer.skills))
134
- throw new Error("AgentRadar did not return your skills");
135
- const written = await writeManagedSkills(skillsDir, answer.skills);
136
- deps.log(`Installed ${written.length} AgentRadar skill(s) in ${skillsDir}.`);
137
- }
138
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
- export async function awpRequest(runnerId, method, params, deps) {
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");
@@ -663,14 +663,6 @@ const isManaged = (dir) => {
663
663
  const st = fs.lstatSync(dir, { throwIfNoEntry: false });
664
664
  return Boolean(st?.isDirectory() && fs.existsSync(join(dir, MANAGED_MARK)));
665
665
  };
666
- const sameSkill = (dir, files) => {
667
- try {
668
- const have = fs.readdirSync(dir, { recursive: true }).filter((p) => p !== MANAGED_MARK && fs.statSync(join(dir, p)).isFile());
669
- return have.length === files.length && files.every((f) => fs.readFileSync(join(dir, f.path), "utf8") === f.content);
670
- } catch {
671
- return false;
672
- }
673
- };
674
666
 
675
667
  export function writeManagedSkills(skillsDir, skills) {
676
668
  fs.mkdirSync(skillsDir, { recursive: true });
@@ -687,11 +679,7 @@ export function writeManagedSkills(skillsDir, skills) {
687
679
  const bad = files.find((f) => typeof f?.path !== "string" || typeof f?.content !== "string" || isAbsolute(f.path)
688
680
  || normalize(f.path).split(sep).includes("..") || [".", MANAGED_MARK].includes(normalize(f.path)));
689
681
  if (bad) { console.warn(`[feeder] skill sync: ${name} has an unsafe file path, skipped`); continue; }
690
- // An unchanged skill is not rewritten: the agent's CLI may be reading it (Codex skipped a SKILL.md that a
691
- // periodic resync had just removed).
692
- if (sameSkill(target, files)) { written.add(name); continue; }
693
682
  const staging = join(skillsDir, `.${name}.sync-${process.pid}`);
694
- const old = join(skillsDir, `.${name}.old-${process.pid}`);
695
683
  try {
696
684
  fs.rmSync(staging, { recursive: true, force: true });
697
685
  for (const f of files) {
@@ -699,16 +687,12 @@ export function writeManagedSkills(skillsDir, skills) {
699
687
  fs.writeFileSync(join(staging, f.path), f.content);
700
688
  }
701
689
  fs.writeFileSync(join(staging, MANAGED_MARK), "Written by the AgentRadar runner; replaced on every sync.\n");
702
- // Rename aside, then into place: the skill is missing for two renames, not for a recursive delete.
703
- fs.rmSync(old, { recursive: true, force: true });
704
- if (fs.existsSync(target)) fs.renameSync(target, old);
690
+ fs.rmSync(target, { recursive: true, force: true });
705
691
  fs.renameSync(staging, target);
706
- fs.rmSync(old, { recursive: true, force: true });
707
692
  written.add(name);
708
693
  } catch (err) {
709
694
  // One skill that cannot be written keeps its previous copy and does not stop the rest of the sync.
710
695
  fs.rmSync(staging, { recursive: true, force: true });
711
- if (!fs.existsSync(target) && fs.existsSync(old)) fs.renameSync(old, target);
712
696
  if (isManaged(target)) written.add(name);
713
697
  console.warn(`[feeder] skill sync: ${name} not written: ${err instanceof Error ? err.message : String(err)}`);
714
698
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentradar-cli",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },