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 +23 -9
- package/dist/setup.js +6 -5
- package/package.json +1 -1
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 <
|
|
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
|
-
|
|
123
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 <
|
|
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 });
|