agentradar-cli 0.3.15 → 0.3.17

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
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync, statSync } from "node:fs";
2
3
  import { createRequire } from "node:module";
3
4
  import { homedir } from "node:os";
5
+ import { resolve } from "node:path";
4
6
  import { installVendorSkill } from "./skill-install.js";
5
7
  import { startChannel } from "./channel.js";
6
8
  import { runRestartSelftest, startVendorRunner } from "./runner.js";
@@ -19,6 +21,15 @@ export const runArgs = (raw) => raw[0] !== "run" ? raw
19
21
  : raw[1] === "join" ? raw.slice(1)
20
22
  : /^https?:\/\//.test(raw[1] ?? "") ? ["join", ...raw.slice(1)]
21
23
  : ["call", ...raw.slice(1)];
24
+ // The join link carries the secret that claims the agent's key, and ps shows argv to every user on the machine:
25
+ // `--link-file <path>` or `-` (stdin) keeps it off. `again` is how the CLI's own "run it again" lines name it.
26
+ export const joinLink = (urls, linkFile, stdin = () => readFileSync(0, "utf8"), warn = console.error) => {
27
+ if (linkFile && statSync(linkFile).mode & 0o077)
28
+ warn(`Warning: ${linkFile} is readable by other users on this machine; chmod 600 it.`);
29
+ return linkFile ? { link: readFileSync(linkFile, "utf8").trim(), again: `--link-file ${resolve(linkFile)}` }
30
+ : urls[0] === "-" ? { link: stdin().trim(), again: "-" }
31
+ : { link: urls[0], again: undefined };
32
+ };
22
33
  const USAGE = [
23
34
  "Usage: agentradar <noun> [<sub-noun>] <verb> [--<flag> <value> | --<flag>-file <path> ...] [--dry-run] [--json] [--runner <id>]",
24
35
  " Every agent command comes from the server. agentradar --help lists the groups (as $AR_CLI_RUNNER_ID),",
@@ -30,8 +41,8 @@ const USAGE = [
30
41
  " The runner is $AR_CLI_RUNNER_ID, or --runner <id>. --dry-run prints the exact call and sends nothing.",
31
42
  " Exit codes: 0 done; 1 refused or unreachable (the reason is on stderr); 2 the command was wrong.",
32
43
  "Local commands:",
33
- " agentradar join <join-link> [--name <n>] [--model <m>] [--runtime claude|codex|other] [--trust] [--attach-tmux <pane> | --attach-current] [--session <id>] [--no-service | --service]",
34
- " agentradar join cancel <join-link|request-id>",
44
+ " agentradar join <join-link> | --link-file <path> | - (link on stdin) [--name <n>] [--model <m>] [--runtime claude|codex|other] [--trust] [--attach-tmux <pane> | --attach-current] [--session <id>] [--no-service | --service]",
45
+ " agentradar join cancel <request-id> | --link-file <path> | - (link on stdin) | <join-link>",
35
46
  " agentradar setup [--trust] [--harness claude|codex]",
36
47
  " agentradar leave <runner-id>",
37
48
  " agentradar channel <runner-id>",
@@ -119,25 +130,26 @@ async function main() {
119
130
  return;
120
131
  }
121
132
  if (args[0] === "join" && args[1] === "cancel") {
122
- if (args.length !== 3 || args[2].startsWith("-")) {
123
- console.error("Usage: agentradar join cancel <join-link|request-id>");
133
+ const fromFile = args.length === 4 && args[2] === "--link-file";
134
+ if (!fromFile && (args.length !== 3 || (args[2].startsWith("-") && args[2] !== "-"))) {
135
+ console.error("Usage: agentradar join cancel <request-id> | --link-file <path> | - (link on stdin) | <join-link>");
124
136
  process.exitCode = 1;
125
137
  return;
126
138
  }
127
- process.exitCode = await cancelJoin(args[2]);
139
+ process.exitCode = await cancelJoin(fromFile || args[2] === "-" ? joinLink([args[2]], fromFile ? args[3] : undefined).link : args[2]);
128
140
  return;
129
141
  }
130
142
  if (args[0] === "join") {
131
143
  const named = {};
132
144
  const urls = [];
133
145
  for (let i = 1; i < args.length; i++) {
134
- if (["--name", "--model", "--attach-tmux", "--session", "--runtime"].includes(args[i]))
146
+ if (["--name", "--model", "--attach-tmux", "--session", "--runtime", "--link-file"].includes(args[i]))
135
147
  named[args[i].slice(2)] = args[++i] ?? "-";
136
148
  else if (!["--trust", "--attach-current", "--allow-bypass", "--revoke-bypass", "--feeder", "--service", "--no-service"].includes(args[i]))
137
149
  urls.push(args[i]);
138
150
  }
139
- if (urls.length !== 1 || [urls[0], ...Object.values(named)].some((value) => value.startsWith("-")) || (named["attach-tmux"] && args.includes("--attach-current")) || (args.includes("--service") && args.includes("--no-service"))) {
140
- console.error("Usage: agentradar join <join-link> [--name <your name>] [--model <your model id>] [--runtime <claude|codex|other>] [--trust] [--attach-tmux <session[:window.pane]> | --attach-current (Codex and other CLIs)] [--session <claude session id>] [--revoke-bypass] [--no-service | --service]");
151
+ if (urls.length !== (named["link-file"] ? 0 : 1) || [...urls.filter((url) => url !== "-"), ...Object.values(named)].some((value) => value.startsWith("-")) || (named["attach-tmux"] && args.includes("--attach-current")) || (args.includes("--service") && args.includes("--no-service"))) {
152
+ console.error("Usage: agentradar join <join-link> | --link-file <path> | - (the link on stdin) [--name <your name>] [--model <your model id>] [--runtime <claude|codex|other>] [--trust] [--attach-tmux <session[:window.pane]> | --attach-current (Codex and other CLIs)] [--session <claude session id>] [--revoke-bypass] [--no-service | --service]");
141
153
  process.exitCode = 1;
142
154
  return;
143
155
  }
@@ -148,7 +160,9 @@ async function main() {
148
160
  process.exitCode = 1;
149
161
  return;
150
162
  }
151
- process.exitCode = await joinOrganisation(urls[0], {
163
+ const { link, again } = joinLink(urls, named["link-file"]);
164
+ process.exitCode = await joinOrganisation(link, {
165
+ again,
152
166
  harness: runtime.harness, trust: args.includes("--trust"), agentName: named.name, model: named.model, session: named.session,
153
167
  attach: args.includes("--attach-current") ? "current" : named["attach-tmux"],
154
168
  revokeBypass: args.includes("--revoke-bypass"), feeder: args.includes("--feeder"), service: args.includes("--service"),
package/dist/setup.js CHANGED
@@ -904,7 +904,7 @@ export async function joinOrganisation(enrolmentUrl, deps = {}) {
904
904
  await saved?.();
905
905
  log(`Runner installed → ${runnerEnv}`);
906
906
  if (bypass)
907
- log(bypassOnNotice(enrolmentUrl));
907
+ log(bypassOnNotice(deps.again ?? enrolmentUrl));
908
908
  // Best effort: a failed first sync never stops the join; the service's feeder syncs them again.
909
909
  await syncJoinedSkills(runnerId, { home, fetch: fetchImpl, log }).catch((error) => {
910
910
  log(`Your AgentRadar skills are not installed yet (${String(error?.message ?? error).split("\n")[0]}); the runner installs them once it is running.`);
@@ -1069,7 +1069,7 @@ You can restart now or after the approval: until then the channel waits. You do
1069
1069
  const approvedBypass = now.bypassPermissions === true;
1070
1070
  const bypass = localBypass(approvedBypass, false);
1071
1071
  if (bypass)
1072
- log(bypassOnNotice(enrolmentUrl));
1072
+ log(bypassOnNotice(deps.again ?? enrolmentUrl));
1073
1073
  return connected(runnerId, projectPath, approvedBypass, bypass);
1074
1074
  }
1075
1075
  if (poll > 1 && (poll - 1) % 3 === 0)
@@ -1084,7 +1084,7 @@ You can restart now or after the approval: until then the channel waits. You do
1084
1084
  }
1085
1085
  for (let poll = 1;; poll++) {
1086
1086
  if (poll > (deps.polls ?? Infinity)) {
1087
- log(`Still waiting for approval. Run this same command again: agentradar run ${enrolmentUrl}`);
1087
+ log(`Still waiting for approval. Run this same command again: agentradar run ${deps.again ? `join ${deps.again}` : enrolmentUrl}`);
1088
1088
  return 0;
1089
1089
  }
1090
1090
  let response;
@@ -1138,7 +1138,8 @@ export async function cancelJoin(target, deps = {}) {
1138
1138
  }
1139
1139
  }
1140
1140
  if (!state || !statePath) {
1141
- log(`No join request from this machine for ${target}.`);
1141
+ // A link carries the claim token; never echo it.
1142
+ log(`No join request from this machine for ${/^https?:\/\//.test(target) ? "that join link" : target}.`);
1142
1143
  return 1;
1143
1144
  }
1144
1145
  if (state.agentId) {
@@ -1148,7 +1149,7 @@ export async function cancelJoin(target, deps = {}) {
1148
1149
  // The secret goes only back to the AgentRadar host that issued it.
1149
1150
  const origin = token ? new URL(target).origin : state.approvalUrl ? new URL(state.approvalUrl).origin : "";
1150
1151
  if (!origin || !trustedMcpEndpoint(origin)) {
1151
- log("Pass the join link instead of the request id: agentradar join cancel <join-link>");
1152
+ log("Pass the join link instead of the request id: agentradar join cancel --link-file <path>");
1152
1153
  return 1;
1153
1154
  }
1154
1155
  const response = await postJson(fetchImpl, new URL(`/api/join-requests/${encodeURIComponent(state.requestId)}/cancel`, origin), { claimSecret: state.claimSecret });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentradar-cli",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },