@scp-dev/cli 0.2.0 → 0.2.1
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 +1 -0
- package/dist/{chunk-GH7AXFUL.js → chunk-25ZVL5LD.js} +8 -10
- package/dist/{chunk-RXKBND3T.js → chunk-HF4KP6AR.js} +229 -13
- package/dist/{chunk-5DYFYLAQ.js → chunk-KC5AV5NQ.js} +3 -5
- package/dist/{chunk-M2DCR2J4.js → chunk-MNTIEWKF.js} +13 -3
- package/dist/{chunk-U2RSSTNW.js → chunk-MXQ7B3CQ.js} +3 -5
- package/dist/{chunk-WV5FINTW.js → chunk-NJLFHBSS.js} +20 -10
- package/dist/chunk-ZLL5EGI2.js +805 -0
- package/dist/{claude-YJLGFQ2P.js → claude-WTCPUYU3.js} +21 -11
- package/dist/cli.js +1 -1
- package/dist/codex-YZZNH6IQ.js +565 -0
- package/dist/{daemon-SUTLTY5U.js → daemon-OMJY7T2F.js} +1 -1
- package/dist/{hooks-EI7M3YKY.js → hooks-4B5NOH7X.js} +19 -3
- package/dist/install-2ZM3IYXG.js +14 -0
- package/dist/join-BH5CS6SL.js +45 -0
- package/dist/{login-V2WT7QX5.js → login-UB72MU7W.js} +4 -7
- package/dist/{main-HPBTO3JK.js → main-I4F53MMA.js} +79 -31
- package/dist/{mcp-DGAQNFKO.js → mcp-RXAVQBMP.js} +11 -11
- package/dist/{peek-WLG3ZROU.js → peek-M2HQVO4O.js} +2 -3
- package/dist/{pi-HMESQA64.js → pi-IAVFVOOO.js} +2 -3
- package/dist/{relay-5Z7IEXKD.js → relay-QDX2BNZH.js} +6 -4
- package/dist/{remote-FYN5ZXE4.js → remote-2OWBWLJH.js} +3 -5
- package/dist/{setup-LXG346FJ.js → setup-HPH7HE6O.js} +5 -6
- package/dist/{spawn-DLPMAS5M.js → spawn-V66GYUNC.js} +6 -7
- package/dist/{up-GPM4LWY3.js → up-2YGEDA5J.js} +6 -9
- package/package.json +7 -5
- package/dist/chunk-3RRPUYTE.js +0 -147
- package/dist/chunk-3V3DRXB2.js +0 -340
- package/dist/chunk-E53YHYEP.js +0 -305
- package/dist/install-MYHVFKT4.js +0 -15
|
@@ -0,0 +1,805 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SELF,
|
|
3
|
+
capture,
|
|
4
|
+
note,
|
|
5
|
+
parseSeconds
|
|
6
|
+
} from "./chunk-MNTIEWKF.js";
|
|
7
|
+
|
|
8
|
+
// src/relay.ts
|
|
9
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
10
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
11
|
+
import { randomUUID } from "crypto";
|
|
12
|
+
import { CLI_NAME as CLI_NAME3 } from "@scp-dev/core";
|
|
13
|
+
|
|
14
|
+
// src/common.ts
|
|
15
|
+
import { spawn } from "child_process";
|
|
16
|
+
import { closeSync, openSync, statSync, truncateSync } from "fs";
|
|
17
|
+
import { join as join2 } from "path";
|
|
18
|
+
import { CLI_NAME as CLI_NAME2 } from "@scp-dev/core";
|
|
19
|
+
import { connectBroker, privateDir, relayDir as relayDir2 } from "@scp-dev/broker";
|
|
20
|
+
|
|
21
|
+
// src/identity.ts
|
|
22
|
+
import { existsSync, realpathSync } from "fs";
|
|
23
|
+
import { tmpdir } from "os";
|
|
24
|
+
import { basename, dirname, isAbsolute, join, resolve } from "path";
|
|
25
|
+
import { shortHostname } from "@scp-dev/broker";
|
|
26
|
+
import { LIMITS, validHandle, validHarness } from "@scp-dev/core";
|
|
27
|
+
var SHORT = { "claude-code": "claude" };
|
|
28
|
+
var SHELLS = /* @__PURE__ */ new Set(["sh", "bash", "zsh", "dash", "fish"]);
|
|
29
|
+
var LABEL_KEY = /^[a-z0-9][a-z0-9._-]{0,31}$/;
|
|
30
|
+
function detectHarness(env) {
|
|
31
|
+
if (env.GROK_SESSION_ID || env.GROK_HOOK_EVENT) return "grok";
|
|
32
|
+
if (env.MUSE_SESSION_ID) return "muse";
|
|
33
|
+
if (env.OPENCODE === "1") return "opencode";
|
|
34
|
+
if (env.ANTIGRAVITY_AGENT === "1" || env.ANTIGRAVITY_CONVERSATION_ID) return "agy";
|
|
35
|
+
if (env.CLAUDECODE === "1") return "claude-code";
|
|
36
|
+
if (env.CODEX_THREAD_ID) return "codex";
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
function normalizeHarness(raw, fallback) {
|
|
40
|
+
const name = (raw ?? "").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+/, "").slice(0, 32).replace(/-+$/, "");
|
|
41
|
+
if (name === "claude") return "claude-code";
|
|
42
|
+
return validHarness(name) ? name : fallback;
|
|
43
|
+
}
|
|
44
|
+
function hookHarness(argument, env) {
|
|
45
|
+
if (!argument) return detectHarness(env) ?? "hook";
|
|
46
|
+
const named = normalizeHarness(argument, "hook");
|
|
47
|
+
return named === "claude-code" && (env.GROK_SESSION_ID || env.GROK_HOOK_EVENT) ? "grok" : named;
|
|
48
|
+
}
|
|
49
|
+
function resolveHarness(explicit, env, fallback) {
|
|
50
|
+
const named = explicit ?? env.SCP_HARNESS;
|
|
51
|
+
return named ? normalizeHarness(named, fallback) : detectHarness(env) ?? normalizeHarness(fallback, "mcp");
|
|
52
|
+
}
|
|
53
|
+
function gitToplevel(cwd) {
|
|
54
|
+
for (let dir = resolve(cwd); ; dir = dirname(dir)) {
|
|
55
|
+
if (existsSync(join(dir, ".git"))) return dir;
|
|
56
|
+
if (dirname(dir) === dir) return void 0;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function toHandle(raw) {
|
|
60
|
+
let handle = raw.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z0-9]+/, "").slice(0, 63).replace(/[-.]+$/, "");
|
|
61
|
+
if (!handle) handle = "agent";
|
|
62
|
+
if (!validHandle(handle)) handle = `${handle.slice(0, 56)}-agent`;
|
|
63
|
+
return handle;
|
|
64
|
+
}
|
|
65
|
+
function deriveHandle(harness, cwd) {
|
|
66
|
+
const top = gitToplevel(cwd) ?? resolve(cwd);
|
|
67
|
+
return toHandle(`${SHORT[harness] ?? harness}-${basename(top) || "root"}`);
|
|
68
|
+
}
|
|
69
|
+
function parseLabels(value) {
|
|
70
|
+
const labels = {};
|
|
71
|
+
for (const pair of (value ?? "").split(",")) {
|
|
72
|
+
const eq = pair.indexOf("=");
|
|
73
|
+
const key = pair.slice(0, eq).trim();
|
|
74
|
+
if (eq > 0 && LABEL_KEY.test(key)) labels[key] = pair.slice(eq + 1).trim();
|
|
75
|
+
}
|
|
76
|
+
return labels;
|
|
77
|
+
}
|
|
78
|
+
function labelsFor(env, host = shortHostname()) {
|
|
79
|
+
const labels = parseLabels(env.SCP_LABELS);
|
|
80
|
+
labels.machine ??= host;
|
|
81
|
+
return labels;
|
|
82
|
+
}
|
|
83
|
+
function processKey(harnessPid2, startOf = psStart) {
|
|
84
|
+
const started = startOf(harnessPid2);
|
|
85
|
+
return started ? `pid:${harnessPid2}:${started}` : `pid:${harnessPid2}`;
|
|
86
|
+
}
|
|
87
|
+
function sessionKey(env, harnessProcess) {
|
|
88
|
+
return env.SCP_SESSION ? `env:${env.SCP_SESSION}` : harnessProcess;
|
|
89
|
+
}
|
|
90
|
+
function psStart(pid) {
|
|
91
|
+
try {
|
|
92
|
+
const started = (capture(["ps", "-o", "lstart=", "-p", String(pid)], { PATH: process.env.PATH ?? "/usr/bin:/bin", LC_ALL: "C" }) ?? "").trim().replace(/\s+/g, "-");
|
|
93
|
+
return /^[A-Za-z0-9:-]{1,64}$/.test(started) ? started : void 0;
|
|
94
|
+
} catch {
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function parsePsCommTable(text) {
|
|
99
|
+
const table = /* @__PURE__ */ new Map();
|
|
100
|
+
for (const line of text.split("\n")) {
|
|
101
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(\S+\s+\S+\s+\S+\s+\S+\s+\S+)\s+(\S.*?)\s*$/.exec(line);
|
|
102
|
+
if (match) table.set(Number(match[1]), { ppid: Number(match[2]), start: match[3].replace(/\s+/g, "-"), args: match[4] });
|
|
103
|
+
}
|
|
104
|
+
return table;
|
|
105
|
+
}
|
|
106
|
+
function readPsTable() {
|
|
107
|
+
try {
|
|
108
|
+
const table = capture(["ps", "-ww", "-axo", "pid=,ppid=,lstart=,args="], { PATH: process.env.PATH ?? "/usr/bin:/bin", LC_ALL: "C" });
|
|
109
|
+
return table === void 0 ? void 0 : parsePsCommTable(table);
|
|
110
|
+
} catch {
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
var PASS_THROUGH = /* @__PURE__ */ new Set([
|
|
115
|
+
...SHELLS,
|
|
116
|
+
"ash",
|
|
117
|
+
"ksh",
|
|
118
|
+
"env",
|
|
119
|
+
"timeout",
|
|
120
|
+
"nohup",
|
|
121
|
+
"nice",
|
|
122
|
+
"time",
|
|
123
|
+
"stdbuf",
|
|
124
|
+
"xargs",
|
|
125
|
+
"script",
|
|
126
|
+
"caffeinate",
|
|
127
|
+
"flock",
|
|
128
|
+
"sandbox-exec",
|
|
129
|
+
"bwrap",
|
|
130
|
+
"codex-linux-sandbox",
|
|
131
|
+
"codex-linux-san"
|
|
132
|
+
]);
|
|
133
|
+
var INTERPRETERS = /* @__PURE__ */ new Set(["node", "nodejs", "bun", "deno", "ruby", "perl"]);
|
|
134
|
+
var RUNNERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn"]);
|
|
135
|
+
var FETCHERS = /* @__PURE__ */ new Set(["npx", "bunx", "pnpx", "uvx"]);
|
|
136
|
+
var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["tsx", "ts-node", "vite-node", "esno"]);
|
|
137
|
+
var INLINE = /* @__PURE__ */ new Set(["-c", "-e", "--eval", "-p", "--print", "-"]);
|
|
138
|
+
var VALUED = /* @__PURE__ */ new Set(["-r", "--require", "--import", "--loader", "--experimental-loader", "-I", "-M", "-W", "-X", "-C", "--conditions", "--preload"]);
|
|
139
|
+
var HARNESSES = /* @__PURE__ */ new Set([
|
|
140
|
+
"claude",
|
|
141
|
+
"claude-code",
|
|
142
|
+
"codex",
|
|
143
|
+
"opencode",
|
|
144
|
+
"opencode-ai",
|
|
145
|
+
"grok",
|
|
146
|
+
"grok-cli",
|
|
147
|
+
"muse",
|
|
148
|
+
"agy",
|
|
149
|
+
"antigravity",
|
|
150
|
+
"gemini",
|
|
151
|
+
"gemini-cli",
|
|
152
|
+
"pi-coding-agent"
|
|
153
|
+
]);
|
|
154
|
+
var INSTALLED = /(^|\/)(node_modules|site-packages|dist-packages|\.npm|\.bun|\.cache|\.local|\.cargo|\.volta|\.nvm|\.pyenv|\.rbenv|Cellar|Applications)(\/|$)|^\/(opt|usr|Library|nix)\//;
|
|
155
|
+
function command(args) {
|
|
156
|
+
return basename(args.split(/\s+/)[0].replace(/^-/, "")).toLowerCase();
|
|
157
|
+
}
|
|
158
|
+
var namesHarness = (words) => words.some((word) => !word.startsWith("-") && word.split("/").some((part) => HARNESSES.has(part.toLowerCase().replace(/\..*$/, ""))));
|
|
159
|
+
var roots;
|
|
160
|
+
function workspaceRoots() {
|
|
161
|
+
if (roots) return roots;
|
|
162
|
+
const real = (path) => {
|
|
163
|
+
try {
|
|
164
|
+
return realpathSync(path);
|
|
165
|
+
} catch {
|
|
166
|
+
return path;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const found = [process.cwd(), tmpdir(), "/tmp", "/private/tmp", "/var/folders", "/private/var/folders"];
|
|
170
|
+
try {
|
|
171
|
+
const top = capture(["git", "rev-parse", "--show-toplevel"]);
|
|
172
|
+
if (top !== void 0) found.push(top.trim());
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
roots = [...new Set(found.filter(Boolean).map(real))];
|
|
176
|
+
return roots;
|
|
177
|
+
}
|
|
178
|
+
function ownScript(operand) {
|
|
179
|
+
if (operand === void 0 || operand === "-") return true;
|
|
180
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(operand) || operand.startsWith("~") || INSTALLED.test(operand)) return false;
|
|
181
|
+
if (!isAbsolute(operand)) return !operand.split("/").includes("..");
|
|
182
|
+
let path = operand;
|
|
183
|
+
try {
|
|
184
|
+
path = realpathSync(operand);
|
|
185
|
+
} catch {
|
|
186
|
+
}
|
|
187
|
+
if (INSTALLED.test(path)) return false;
|
|
188
|
+
return workspaceRoots().some((root) => path === root || path.startsWith(`${root}/`));
|
|
189
|
+
}
|
|
190
|
+
function helper(args) {
|
|
191
|
+
const words = args.split(/\s+/);
|
|
192
|
+
if (namesHarness(words.slice(1))) return false;
|
|
193
|
+
const name = command(args);
|
|
194
|
+
let rest = words.slice(1);
|
|
195
|
+
if (FETCHERS.has(name)) {
|
|
196
|
+
while (rest[0]?.startsWith("-")) rest = rest.slice(1);
|
|
197
|
+
return SCRIPT_RUNNERS.has(rest[0] ?? "") && rest.slice(1).every((word) => word.startsWith("-") || ownScript(word));
|
|
198
|
+
}
|
|
199
|
+
if (name === "uv" || RUNNERS.has(name)) {
|
|
200
|
+
if (rest[0] !== "run") return false;
|
|
201
|
+
if (name === "uv") {
|
|
202
|
+
rest = rest.slice(1);
|
|
203
|
+
while (rest[0]?.startsWith("-")) rest = rest.slice(1);
|
|
204
|
+
if (rest[0] === void 0) return true;
|
|
205
|
+
const inner = rest.join(" ");
|
|
206
|
+
return /^python[0-9.]*$/.test(command(inner)) || INTERPRETERS.has(command(inner)) ? helper(inner) : ownScript(rest[0]);
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
if (!INTERPRETERS.has(name) && !/^python[0-9.]*$/.test(name)) return false;
|
|
211
|
+
if (name === "bun" && rest[0] === "x") return false;
|
|
212
|
+
if ((name === "bun" || name === "deno") && rest[0] === "run") {
|
|
213
|
+
rest = rest.slice(1);
|
|
214
|
+
if (name === "deno") while (rest[0]?.startsWith("-")) rest = rest.slice(1);
|
|
215
|
+
const target = rest.find((word) => !word.startsWith("-"));
|
|
216
|
+
if (target !== void 0 && !/[/.]/.test(target)) return false;
|
|
217
|
+
}
|
|
218
|
+
for (let i = 0; i < rest.length; i++) {
|
|
219
|
+
const word = rest[i];
|
|
220
|
+
if (word === "-m") return false;
|
|
221
|
+
if (INLINE.has(word)) return true;
|
|
222
|
+
if (VALUED.has(word)) {
|
|
223
|
+
if (!ownScript(rest[++i])) return false;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (!word.startsWith("-")) return ownScript(word);
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
function harnessLineage(pid, table = readPsTable()) {
|
|
231
|
+
const keys = lineage(pid, table);
|
|
232
|
+
let first = -1;
|
|
233
|
+
for (let i = 0, at = pid; i < keys.length; i++, at = table.get(at).ppid) {
|
|
234
|
+
const args = table.get(at).args;
|
|
235
|
+
if (PASS_THROUGH.has(command(args))) continue;
|
|
236
|
+
if (!helper(args)) return keys.slice(0, i + 1);
|
|
237
|
+
if (first < 0) first = i;
|
|
238
|
+
}
|
|
239
|
+
return first < 0 ? [] : keys;
|
|
240
|
+
}
|
|
241
|
+
var INITS = /* @__PURE__ */ new Set(["init", "systemd", "launchd"]);
|
|
242
|
+
function hiddenHarness(pid, table = readPsTable()) {
|
|
243
|
+
if (!table || harnessLineage(pid, table).length) return false;
|
|
244
|
+
const seen = /* @__PURE__ */ new Set();
|
|
245
|
+
let at = pid;
|
|
246
|
+
while (at > 1 && !seen.has(at)) {
|
|
247
|
+
seen.add(at);
|
|
248
|
+
const entry = table.get(at);
|
|
249
|
+
if (!entry) return false;
|
|
250
|
+
at = entry.ppid;
|
|
251
|
+
}
|
|
252
|
+
const init = table.get(1);
|
|
253
|
+
return at === 1 && init !== void 0 && !INITS.has(command(init.args));
|
|
254
|
+
}
|
|
255
|
+
function lineage(harnessPid2, table = readPsTable()) {
|
|
256
|
+
const keys = [];
|
|
257
|
+
const seen = /* @__PURE__ */ new Set();
|
|
258
|
+
for (let pid = harnessPid2; pid > 1 && keys.length < LIMITS.lineageMax && !seen.has(pid); ) {
|
|
259
|
+
const entry = table?.get(pid);
|
|
260
|
+
if (!entry) break;
|
|
261
|
+
seen.add(pid);
|
|
262
|
+
keys.push(/^[A-Za-z0-9:-]{1,64}$/.test(entry.start) ? `pid:${pid}:${entry.start}` : `pid:${pid}`);
|
|
263
|
+
pid = entry.ppid;
|
|
264
|
+
}
|
|
265
|
+
return keys;
|
|
266
|
+
}
|
|
267
|
+
function spawnerKey(pid = process.pid, table = readPsTable()) {
|
|
268
|
+
const key = lineage(pid, table)[0];
|
|
269
|
+
return key?.startsWith(`pid:${pid}:`) ? key : void 0;
|
|
270
|
+
}
|
|
271
|
+
function psParent(pid) {
|
|
272
|
+
const match = /^\s*(\d+)\s+(.+?)\s*$/.exec(capture(["ps", "-o", "ppid=,comm=", "-p", String(pid)], { PATH: process.env.PATH ?? "/usr/bin:/bin" }) ?? "");
|
|
273
|
+
return match ? { ppid: Number(match[1]), comm: match[2] } : void 0;
|
|
274
|
+
}
|
|
275
|
+
function harnessPid(ppid = process.ppid, parentOf = psParent) {
|
|
276
|
+
const parent = parentOf(ppid);
|
|
277
|
+
if (parent && parent.ppid > 1 && SHELLS.has(basename(parent.comm.replace(/^-/, "")))) return parent.ppid;
|
|
278
|
+
return ppid;
|
|
279
|
+
}
|
|
280
|
+
function hookHarnessPid(env, harness, ppid = process.ppid, parentOf = psParent) {
|
|
281
|
+
const named = harness === "claude-code" ? env.CLAUDE_PID : harness === "opencode" ? env.OPENCODE_PID : void 0;
|
|
282
|
+
const pid = Number(named);
|
|
283
|
+
if (Number.isInteger(pid) && pid > 1) return pid;
|
|
284
|
+
return harnessPid(ppid, parentOf);
|
|
285
|
+
}
|
|
286
|
+
function relayIdentity(input) {
|
|
287
|
+
const harness = resolveHarness(input.harness, input.env, input.fallbackHarness);
|
|
288
|
+
const cwd = isAbsolute(input.cwd) ? input.cwd : resolve(input.cwd);
|
|
289
|
+
const handle = input.as ?? input.env.SCP_NAME ?? deriveHandle(harness, cwd);
|
|
290
|
+
const pid = input.harnessPid(harness);
|
|
291
|
+
const harnessProcess = processKey(pid, input.startOf);
|
|
292
|
+
const keys = input.lineageOf ? input.lineageOf(pid) : lineage(pid);
|
|
293
|
+
return {
|
|
294
|
+
handle,
|
|
295
|
+
harness,
|
|
296
|
+
cwd,
|
|
297
|
+
session: sessionKey(input.env, harnessProcess),
|
|
298
|
+
process: harnessProcess,
|
|
299
|
+
labels: labelsFor(input.env),
|
|
300
|
+
...input.env.SCP_PARENT ? { parent: input.env.SCP_PARENT } : {},
|
|
301
|
+
...keys.length ? { lineage: keys } : {},
|
|
302
|
+
...input.env.SCP_GRANT ? { grant: input.env.SCP_GRANT } : {}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/session.ts
|
|
307
|
+
import { CLI_NAME } from "@scp-dev/core";
|
|
308
|
+
import { readSessionMarker, relayDir } from "@scp-dev/broker";
|
|
309
|
+
function membership(env, session) {
|
|
310
|
+
const marker = readSessionMarker(relayDir(env), session);
|
|
311
|
+
if (marker?.joined === false) return { dark: `this session left the relay; ${CLI_NAME} join (or relay_join) joins it again` };
|
|
312
|
+
if (!marker?.joined && joinMode(env) === "manual") return { dark: `this session has not joined the relay; ${CLI_NAME} join <name> (or relay_join) joins it` };
|
|
313
|
+
return marker?.handle ? { handle: marker.handle } : {};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/common.ts
|
|
317
|
+
var RelayUsage = class extends Error {
|
|
318
|
+
};
|
|
319
|
+
var OPTIONS = {
|
|
320
|
+
cloud: { type: "boolean" },
|
|
321
|
+
machine: { type: "string" },
|
|
322
|
+
token: { type: "string" },
|
|
323
|
+
label: { type: "string" },
|
|
324
|
+
"no-browser": { type: "boolean" },
|
|
325
|
+
json: { type: "boolean" },
|
|
326
|
+
channels: { type: "boolean" },
|
|
327
|
+
write: { type: "boolean" },
|
|
328
|
+
print: { type: "boolean" },
|
|
329
|
+
"block-stop-on-unread": { type: "boolean" },
|
|
330
|
+
"push-wait": { type: "string" },
|
|
331
|
+
all: { type: "boolean" },
|
|
332
|
+
as: { type: "string" },
|
|
333
|
+
harness: { type: "string" },
|
|
334
|
+
wait: { type: "string" },
|
|
335
|
+
ack: { type: "boolean" },
|
|
336
|
+
keep: { type: "boolean" },
|
|
337
|
+
"reply-to": { type: "string" },
|
|
338
|
+
"require-reply": { type: "boolean" },
|
|
339
|
+
thread: { type: "string" },
|
|
340
|
+
private: { type: "boolean" },
|
|
341
|
+
attach: { type: "string", multiple: true },
|
|
342
|
+
tool: { type: "string" },
|
|
343
|
+
summary: { type: "string" },
|
|
344
|
+
since: { type: "string" },
|
|
345
|
+
limit: { type: "string" },
|
|
346
|
+
idle: { type: "string" },
|
|
347
|
+
policy: { type: "string" },
|
|
348
|
+
human: { type: "boolean" },
|
|
349
|
+
help: { type: "boolean", short: "h" },
|
|
350
|
+
scope: { type: "string" },
|
|
351
|
+
tree: { type: "boolean" },
|
|
352
|
+
roots: { type: "boolean" },
|
|
353
|
+
contacts: { type: "boolean" },
|
|
354
|
+
activity: { type: "boolean" },
|
|
355
|
+
in: { type: "string" },
|
|
356
|
+
out: { type: "string" },
|
|
357
|
+
allow: { type: "string", multiple: true },
|
|
358
|
+
rules: { type: "string" },
|
|
359
|
+
group: { type: "string" },
|
|
360
|
+
under: { type: "string" },
|
|
361
|
+
ttl: { type: "string" },
|
|
362
|
+
labels: { type: "string" },
|
|
363
|
+
"handback-bytes": { type: "string" },
|
|
364
|
+
"no-push": { type: "boolean" },
|
|
365
|
+
bg: { type: "boolean" },
|
|
366
|
+
fg: { type: "boolean" },
|
|
367
|
+
name: { type: "string" },
|
|
368
|
+
title: { type: "string" },
|
|
369
|
+
judge: { type: "boolean" },
|
|
370
|
+
redacted: { type: "boolean" },
|
|
371
|
+
reason: { type: "string" },
|
|
372
|
+
turns: { type: "string" },
|
|
373
|
+
full: { type: "boolean" },
|
|
374
|
+
who: { type: "boolean" },
|
|
375
|
+
peek: { type: "string" },
|
|
376
|
+
endpoint: { type: "string" },
|
|
377
|
+
live: { type: "boolean" }
|
|
378
|
+
};
|
|
379
|
+
var SPAWN_OPTIONS = [
|
|
380
|
+
"as",
|
|
381
|
+
"harness",
|
|
382
|
+
"human",
|
|
383
|
+
"name",
|
|
384
|
+
"title",
|
|
385
|
+
"scope",
|
|
386
|
+
"rules",
|
|
387
|
+
"in",
|
|
388
|
+
"out",
|
|
389
|
+
"allow",
|
|
390
|
+
"group",
|
|
391
|
+
"under",
|
|
392
|
+
"ttl",
|
|
393
|
+
"labels",
|
|
394
|
+
"peek",
|
|
395
|
+
"handback-bytes",
|
|
396
|
+
"no-push",
|
|
397
|
+
"bg",
|
|
398
|
+
"fg"
|
|
399
|
+
];
|
|
400
|
+
var OBSERVE = /* @__PURE__ */ new Set([
|
|
401
|
+
"list",
|
|
402
|
+
"status",
|
|
403
|
+
"watch",
|
|
404
|
+
"log",
|
|
405
|
+
"policy",
|
|
406
|
+
"machines",
|
|
407
|
+
"rules",
|
|
408
|
+
"group",
|
|
409
|
+
"admin",
|
|
410
|
+
"spawn-env",
|
|
411
|
+
"spawn",
|
|
412
|
+
"why",
|
|
413
|
+
"peek",
|
|
414
|
+
"set-peek"
|
|
415
|
+
]);
|
|
416
|
+
var CLOUD_OBSERVE = /* @__PURE__ */ new Set(["list", "status", "watch", "log", "machines"]);
|
|
417
|
+
var GRANT_TOKEN = /^krt_[0-9a-f]{64}$/;
|
|
418
|
+
var DAEMON_ENV = [
|
|
419
|
+
"PATH",
|
|
420
|
+
"LANG",
|
|
421
|
+
"LC_ALL",
|
|
422
|
+
"LC_CTYPE",
|
|
423
|
+
"TZ",
|
|
424
|
+
"TMPDIR",
|
|
425
|
+
"USER",
|
|
426
|
+
"LOGNAME",
|
|
427
|
+
"SCP_IDLE",
|
|
428
|
+
"HOME",
|
|
429
|
+
"XDG_CONFIG_HOME",
|
|
430
|
+
"SCP_TOKEN",
|
|
431
|
+
"SCP_RELAY",
|
|
432
|
+
"SCP_MACHINE"
|
|
433
|
+
];
|
|
434
|
+
var LOG_MAX_BYTES = 1024 * 1024;
|
|
435
|
+
function startDaemon(dir) {
|
|
436
|
+
privateDir(dir);
|
|
437
|
+
const logFile = join2(dir, "broker.log");
|
|
438
|
+
if ((statSync(logFile, { throwIfNoEntry: false })?.size ?? 0) > LOG_MAX_BYTES) truncateSync(logFile, 0);
|
|
439
|
+
const log = openSync(logFile, "a", 384);
|
|
440
|
+
try {
|
|
441
|
+
const env = { SCP_DIR: dir };
|
|
442
|
+
for (const key of DAEMON_ENV) if (process.env[key] !== void 0) env[key] = process.env[key];
|
|
443
|
+
const child = spawn(SELF[0], [...SELF.slice(1), "daemon"], {
|
|
444
|
+
detached: true,
|
|
445
|
+
stdio: ["ignore", log, log],
|
|
446
|
+
env,
|
|
447
|
+
cwd: dir
|
|
448
|
+
});
|
|
449
|
+
child.unref();
|
|
450
|
+
} finally {
|
|
451
|
+
closeSync(log);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function terminalAttached(streams, bodyFromStdin) {
|
|
455
|
+
if (streams.stdin.isTTY === true) return true;
|
|
456
|
+
return !bodyFromStdin && (streams.stdout.isTTY === true || streams.stderr.isTTY === true);
|
|
457
|
+
}
|
|
458
|
+
function localHandle(raw) {
|
|
459
|
+
const handle = raw.startsWith("me/") ? raw.slice(3) : raw;
|
|
460
|
+
if (!handle) throw new RelayUsage("--as me/ names your own human inbox: use --human");
|
|
461
|
+
if (handle.includes("/")) throw new RelayUsage(`--as names a local handle, not ${JSON.stringify(raw)}`);
|
|
462
|
+
return handle;
|
|
463
|
+
}
|
|
464
|
+
function walkNow() {
|
|
465
|
+
const table = readPsTable();
|
|
466
|
+
return { keys: lineage(process.ppid, table), self: harnessLineage(process.ppid, table) };
|
|
467
|
+
}
|
|
468
|
+
async function open(verb, values, cwd, bodyFromStdin, walk) {
|
|
469
|
+
const named = values.as ?? process.env.SCP_NAME;
|
|
470
|
+
const tty = terminalAttached(process, bodyFromStdin);
|
|
471
|
+
if (named && values.human) throw new RelayUsage("--human and --as (or $SCP_NAME) name two identities; pick one");
|
|
472
|
+
const grant = process.env.SCP_GRANT || void 0;
|
|
473
|
+
const cloud = values.cloud || process.env.SCP_TRANSPORT === "cloud" || verb === "machines";
|
|
474
|
+
if (grant !== void 0) {
|
|
475
|
+
if (!GRANT_TOKEN.test(grant)) {
|
|
476
|
+
throw new RelayUsage(
|
|
477
|
+
`$SCP_GRANT is not one spawn-env grant: pass spawn-env's lines as separate words: env $(${CLI_NAME2} spawn-env ...) cmd; in zsh env \${=G} cmd`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (values.human) throw new RelayUsage("--human and $SCP_GRANT name two identities: unset the grant to act as me/");
|
|
481
|
+
if (cloud) throw new RelayUsage("a spawn-env grant is for this machine's relay broker, not the cloud transport");
|
|
482
|
+
}
|
|
483
|
+
if (cloud) {
|
|
484
|
+
if (!named && !values.human && !tty && !OBSERVE.has(verb)) {
|
|
485
|
+
throw new RelayUsage(
|
|
486
|
+
`${CLI_NAME2} ${verb} needs an identity: --as HANDLE (or $SCP_NAME) for an agent, or --human for your own inbox me/ (no terminal is attached, so me/ is not assumed)`
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const { machineName, openCloud } = await import("./remote-2OWBWLJH.js");
|
|
490
|
+
const handle2 = named ? localHandle(named) : void 0;
|
|
491
|
+
const claim = handle2 ? {
|
|
492
|
+
handle: handle2,
|
|
493
|
+
harness: values.harness ?? process.env.SCP_HARNESS ?? "cli",
|
|
494
|
+
cwd,
|
|
495
|
+
...process.env.SCP_PARENT ? { parent: process.env.SCP_PARENT } : {},
|
|
496
|
+
labels: labelsFor(process.env, machineName(values.machine))
|
|
497
|
+
} : void 0;
|
|
498
|
+
const observe = !handle2 && !values.human && CLOUD_OBSERVE.has(verb);
|
|
499
|
+
return {
|
|
500
|
+
...await openCloud({
|
|
501
|
+
...values.machine !== void 0 ? { machine: values.machine } : {},
|
|
502
|
+
...handle2 ? { handle: handle2 } : {},
|
|
503
|
+
...claim ? { claim } : {},
|
|
504
|
+
tty,
|
|
505
|
+
observe
|
|
506
|
+
}),
|
|
507
|
+
broker: null,
|
|
508
|
+
bound: handle2 ? "as" : observe ? null : "human"
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
if (values.machine !== void 0) throw new RelayUsage("--machine names the machine of the cloud transport: add --cloud");
|
|
512
|
+
const { keys, self } = walk ?? walkNow();
|
|
513
|
+
const joined = !values.as && named && self.length ? membership(process.env, sessionKey(process.env, self.at(-1))) : {};
|
|
514
|
+
const handle = "handle" in joined && joined.handle ? joined.handle : named ? localHandle(named) : void 0;
|
|
515
|
+
const harnessPid2 = self.length ? Number(/^pid:(\d+)/.exec(self.at(-1))?.[1]) : void 0;
|
|
516
|
+
const lineageClaim = keys.length ? { lineage: keys } : {};
|
|
517
|
+
const dir = relayDir2();
|
|
518
|
+
const client = await connectBroker(dir, () => startDaemon(dir));
|
|
519
|
+
try {
|
|
520
|
+
let bound;
|
|
521
|
+
let hello;
|
|
522
|
+
if (handle) {
|
|
523
|
+
bound = "as";
|
|
524
|
+
hello = await client.hello({
|
|
525
|
+
claim: {
|
|
526
|
+
handle,
|
|
527
|
+
exact: true,
|
|
528
|
+
harness: values.harness ?? process.env.SCP_HARNESS ?? "cli",
|
|
529
|
+
cwd,
|
|
530
|
+
...process.env.SCP_PARENT ? { parent: process.env.SCP_PARENT } : {},
|
|
531
|
+
labels: labelsFor(process.env),
|
|
532
|
+
...lineageClaim,
|
|
533
|
+
...grant ? { grant } : {}
|
|
534
|
+
},
|
|
535
|
+
...self.length ? { self } : {},
|
|
536
|
+
brief: true
|
|
537
|
+
});
|
|
538
|
+
} else if (grant) {
|
|
539
|
+
hello = await client.hello({ observe: true, ...lineageClaim, ...self.length ? { self } : {}, grant, cwd, brief: true });
|
|
540
|
+
if (!hello.grant_bound) throw new RelayUsage(`the running ${CLI_NAME2} broker does not check spawn-env grants: let it exit (it stops when idle) and retry`);
|
|
541
|
+
bound = "lineage";
|
|
542
|
+
} else if (values.human || tty) {
|
|
543
|
+
hello = await client.hello({ human: true, tty, cwd, brief: true, ...lineageClaim });
|
|
544
|
+
bound = hello.endpoint?.kind === "human" ? "human" : "lineage";
|
|
545
|
+
} else {
|
|
546
|
+
hello = await client.hello({ observe: true, ...lineageClaim, ...self.length ? { self } : {}, cwd, brief: true });
|
|
547
|
+
bound = hello.endpoint ? "lineage" : null;
|
|
548
|
+
if (!hello.endpoint && !OBSERVE.has(verb)) {
|
|
549
|
+
if (hiddenHarness(process.ppid, readPsTable()))
|
|
550
|
+
throw new RelayUsage(`${CLI_NAME2} ${verb}: sandbox pid namespace: this shell cannot see its harness; use the scp MCP tools`);
|
|
551
|
+
throw new RelayUsage(
|
|
552
|
+
`${CLI_NAME2} ${verb}: no relay agent runs this shell's harness process (${harnessPid2 ? `pid ${harnessPid2}` : "none found"}); ${CLI_NAME2} join <name> puts this session on the relay, or pass --as HANDLE or --human (no terminal is attached, so me/ is not assumed)`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return { client, me: hello.endpoint, broker: hello.broker, where: brokerLine(hello.broker), bound, harnessPid: harnessPid2 };
|
|
557
|
+
} catch (error) {
|
|
558
|
+
client.close();
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function localClient(client, broker, verb) {
|
|
563
|
+
if (!broker) throw new RelayUsage(`${CLI_NAME2} ${verb} is the local broker's; it has no --cloud form`);
|
|
564
|
+
return client;
|
|
565
|
+
}
|
|
566
|
+
function brokerLine(broker) {
|
|
567
|
+
const policy = broker.policy.state === "open" ? `no policy at ${broker.policy.file}, open` : broker.policy.state === "loaded" ? `policy ${broker.policy.file}` : `policy ${broker.policy.file} unusable, sends refused`;
|
|
568
|
+
return `broker ${broker.socket} (pid ${broker.pid}); ${policy}`;
|
|
569
|
+
}
|
|
570
|
+
function selectorList(value) {
|
|
571
|
+
if (value === void 0 || typeof value === "boolean") return void 0;
|
|
572
|
+
return (Array.isArray(value) ? value : [value]).flatMap((v) => v.split(",")).map((v) => v.trim()).filter(Boolean);
|
|
573
|
+
}
|
|
574
|
+
var PRESETS = /* @__PURE__ */ new Set(["open", "tree", "parent", "none"]);
|
|
575
|
+
function preset(value) {
|
|
576
|
+
if (value === void 0) return void 0;
|
|
577
|
+
if (typeof value !== "string" || !PRESETS.has(value)) throw new RelayUsage("--scope takes open, tree, parent or none");
|
|
578
|
+
return value;
|
|
579
|
+
}
|
|
580
|
+
function peekGrant(value) {
|
|
581
|
+
if (value === void 0) return {};
|
|
582
|
+
if (value !== "local") throw new RelayUsage(`--peek takes local (peekable on this machine from its start), not ${JSON.stringify(value)}`);
|
|
583
|
+
return { peek: "local" };
|
|
584
|
+
}
|
|
585
|
+
function grantParams(values) {
|
|
586
|
+
const rules = selectorList(values.rules);
|
|
587
|
+
if (rules && values.scope !== void 0) throw new RelayUsage("--rules and --scope both set a preset; pick one");
|
|
588
|
+
const allow = [...rules ?? [], ...selectorList(values.allow) ?? []];
|
|
589
|
+
const scope = rules ? "none" : preset(values.scope);
|
|
590
|
+
const groups = selectorList(values.group);
|
|
591
|
+
const incoming = selectorList(values.in);
|
|
592
|
+
const outgoing = selectorList(values.out);
|
|
593
|
+
return {
|
|
594
|
+
...values.name ? { name: values.name } : {},
|
|
595
|
+
...values.title !== void 0 ? { title: values.title } : {},
|
|
596
|
+
...values.under ? { under: values.under } : {},
|
|
597
|
+
...scope ? { scope } : {},
|
|
598
|
+
...incoming ? { in: incoming } : {},
|
|
599
|
+
...outgoing ? { out: outgoing } : {},
|
|
600
|
+
...allow.length ? { allow } : {},
|
|
601
|
+
...groups?.length ? { groups } : {},
|
|
602
|
+
...values.ttl !== void 0 ? { ttl_s: ttlSeconds(values.ttl) } : {},
|
|
603
|
+
...values["no-push"] === true ? { no_push: true } : {},
|
|
604
|
+
...peekGrant(values.peek)
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function ttlSeconds(text) {
|
|
608
|
+
try {
|
|
609
|
+
return parseSeconds(text, "--ttl");
|
|
610
|
+
} catch (error) {
|
|
611
|
+
throw new RelayUsage(error instanceof Error ? error.message : String(error));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
615
|
+
function time(iso) {
|
|
616
|
+
const at = new Date(iso);
|
|
617
|
+
return Number.isNaN(at.getTime()) ? iso : `${pad(at.getHours())}:${pad(at.getMinutes())}:${pad(at.getSeconds())}`;
|
|
618
|
+
}
|
|
619
|
+
function stamp(iso) {
|
|
620
|
+
const at = new Date(iso);
|
|
621
|
+
return Number.isNaN(at.getTime()) ? iso : `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())} ${time(iso)}`;
|
|
622
|
+
}
|
|
623
|
+
var CHANNELS_ENV = "SCP_CHANNELS";
|
|
624
|
+
|
|
625
|
+
// src/relay.ts
|
|
626
|
+
var DEFAULT_RELAY = "https://app.scp.dev";
|
|
627
|
+
function configDir(env = process.env) {
|
|
628
|
+
const base = env.XDG_CONFIG_HOME || (env.HOME ? join3(env.HOME, ".config") : void 0);
|
|
629
|
+
if (!base) throw new RelayUsage("neither XDG_CONFIG_HOME nor HOME is set, so there is no place for the relay configuration");
|
|
630
|
+
return join3(base, "scp");
|
|
631
|
+
}
|
|
632
|
+
function checkRelayUrl(text, what = "relay URL") {
|
|
633
|
+
let url;
|
|
634
|
+
try {
|
|
635
|
+
url = new URL(text);
|
|
636
|
+
} catch {
|
|
637
|
+
throw new RelayUsage(`${what} must be an absolute https URL (http is allowed only for localhost)`);
|
|
638
|
+
}
|
|
639
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) || url.username || url.password) {
|
|
640
|
+
throw new RelayUsage(`${what} must be an absolute https URL without credentials (http is allowed only for localhost)`);
|
|
641
|
+
}
|
|
642
|
+
return url.origin;
|
|
643
|
+
}
|
|
644
|
+
function readObject(path) {
|
|
645
|
+
let text;
|
|
646
|
+
try {
|
|
647
|
+
text = readFileSync(path, "utf8");
|
|
648
|
+
} catch (error) {
|
|
649
|
+
if (error.code === "ENOENT") return void 0;
|
|
650
|
+
throw new RelayUsage(`could not read ${path}`);
|
|
651
|
+
}
|
|
652
|
+
try {
|
|
653
|
+
const value = JSON.parse(text);
|
|
654
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error();
|
|
655
|
+
return value;
|
|
656
|
+
} catch {
|
|
657
|
+
throw new RelayUsage(`${path} must contain a JSON object`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function writePrivateJson(path, value) {
|
|
661
|
+
const dir = dirname2(path);
|
|
662
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
663
|
+
chmodSync(dir, 448);
|
|
664
|
+
const temp = join3(dir, `.${randomUUID()}.tmp`);
|
|
665
|
+
try {
|
|
666
|
+
writeFileSync(temp, `${JSON.stringify(value, null, 2)}
|
|
667
|
+
`, { mode: 384, flag: "wx" });
|
|
668
|
+
chmodSync(temp, 384);
|
|
669
|
+
renameSync(temp, path);
|
|
670
|
+
} finally {
|
|
671
|
+
rmSync(temp, { force: true });
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
function activeRelay(env = process.env) {
|
|
675
|
+
if (env.SCP_RELAY !== void 0) return { url: checkRelayUrl(env.SCP_RELAY, "$SCP_RELAY"), source: "SCP_RELAY" };
|
|
676
|
+
const path = join3(configDir(env), "config.json");
|
|
677
|
+
const config = readObject(path);
|
|
678
|
+
if (config?.relay !== void 0) {
|
|
679
|
+
if (typeof config.relay !== "string") throw new RelayUsage(`relay in ${path} must be a URL`);
|
|
680
|
+
return { url: checkRelayUrl(config.relay, `relay in ${path}`), source: "config" };
|
|
681
|
+
}
|
|
682
|
+
return { url: DEFAULT_RELAY, source: "default" };
|
|
683
|
+
}
|
|
684
|
+
function joinMode(env = process.env) {
|
|
685
|
+
try {
|
|
686
|
+
return readObject(join3(configDir(env), "config.json"))?.join === "manual" ? "manual" : "auto";
|
|
687
|
+
} catch {
|
|
688
|
+
return "auto";
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function setJoinMode(env, mode) {
|
|
692
|
+
const path = join3(configDir(env), "config.json");
|
|
693
|
+
writePrivateJson(path, { ...readObject(path), join: mode });
|
|
694
|
+
}
|
|
695
|
+
function readCredentials(env = process.env) {
|
|
696
|
+
const path = join3(configDir(env), "credentials.json");
|
|
697
|
+
const entries = readObject(path) ?? {};
|
|
698
|
+
for (const entry of Object.values(entries)) {
|
|
699
|
+
if (!entry || typeof entry !== "object" || typeof entry.token !== "string" || !entry.token.trim() || typeof entry.saved_at !== "string") {
|
|
700
|
+
throw new RelayUsage(`${path} contains an invalid credential`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return entries;
|
|
704
|
+
}
|
|
705
|
+
function saveCredential(env, origin, credential) {
|
|
706
|
+
const url = checkRelayUrl(origin);
|
|
707
|
+
writePrivateJson(join3(configDir(env), "credentials.json"), { ...readCredentials(env), [url]: { ...credential, saved_at: (/* @__PURE__ */ new Date()).toISOString() } });
|
|
708
|
+
}
|
|
709
|
+
function removeCredential(env, origin) {
|
|
710
|
+
const entries = readCredentials(env);
|
|
711
|
+
const url = checkRelayUrl(origin);
|
|
712
|
+
if (!Object.hasOwn(entries, url)) return false;
|
|
713
|
+
delete entries[url];
|
|
714
|
+
writePrivateJson(join3(configDir(env), "credentials.json"), entries);
|
|
715
|
+
return true;
|
|
716
|
+
}
|
|
717
|
+
function relayCredential(env, origin) {
|
|
718
|
+
const token = env.SCP_TOKEN?.trim();
|
|
719
|
+
if (token) return { token, source: "SCP_TOKEN", problem: null };
|
|
720
|
+
const stored = readCredentials(env)[checkRelayUrl(origin)];
|
|
721
|
+
if (stored) return { token: stored.token, source: "credentials", problem: null };
|
|
722
|
+
return { token: null, source: null, problem: `no login for ${origin}: run ${CLI_NAME3} login, or set SCP_TOKEN` };
|
|
723
|
+
}
|
|
724
|
+
function relayVerb(positionals, asJson, env = process.env) {
|
|
725
|
+
const [action, text] = positionals;
|
|
726
|
+
if (action === "use" && text !== void 0 && positionals.length === 2) {
|
|
727
|
+
const url = checkRelayUrl(text);
|
|
728
|
+
writePrivateJson(join3(configDir(env), "config.json"), { ...readObject(join3(configDir(env), "config.json")), relay: url });
|
|
729
|
+
if (asJson) console.log(JSON.stringify({ url }));
|
|
730
|
+
else console.log(`saved relay ${url}`);
|
|
731
|
+
if (env.SCP_RELAY !== void 0) note("$SCP_RELAY still selects the active relay");
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
if (action === "show" && positionals.length === 1) {
|
|
735
|
+
const relay = activeRelay(env);
|
|
736
|
+
const credential = relayCredential(env, relay.url);
|
|
737
|
+
const result = { ...relay, credential: credential.token ? "found" : "missing", credential_source: credential.source };
|
|
738
|
+
if (asJson) console.log(JSON.stringify(result));
|
|
739
|
+
else console.log(`${relay.url} (${relay.source}); credential ${result.credential}${credential.source ? ` (${credential.source})` : ""}`);
|
|
740
|
+
return 0;
|
|
741
|
+
}
|
|
742
|
+
throw new RelayUsage(`${CLI_NAME3} relay takes use <url> or show`);
|
|
743
|
+
}
|
|
744
|
+
async function loginMain(positionals, values) {
|
|
745
|
+
if (positionals.length) throw new RelayUsage(`${CLI_NAME3} login takes only flags`);
|
|
746
|
+
return (await import("./login-UB72MU7W.js")).login(values);
|
|
747
|
+
}
|
|
748
|
+
function logoutMain(positionals, asJson, env = process.env) {
|
|
749
|
+
if (positionals.length) throw new RelayUsage(`${CLI_NAME3} logout takes no arguments`);
|
|
750
|
+
const { url } = activeRelay(env);
|
|
751
|
+
const removed = removeCredential(env, url);
|
|
752
|
+
if (asJson) console.log(JSON.stringify({ url, removed, env_token: !!env.SCP_TOKEN?.trim() }));
|
|
753
|
+
else console.log(removed ? `logged out of ${url}` : `no stored login for ${url}`);
|
|
754
|
+
if (env.SCP_TOKEN?.trim()) note("$SCP_TOKEN still applies");
|
|
755
|
+
return 0;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
export {
|
|
759
|
+
shortHostname,
|
|
760
|
+
detectHarness,
|
|
761
|
+
normalizeHarness,
|
|
762
|
+
hookHarness,
|
|
763
|
+
gitToplevel,
|
|
764
|
+
deriveHandle,
|
|
765
|
+
processKey,
|
|
766
|
+
sessionKey,
|
|
767
|
+
psStart,
|
|
768
|
+
readPsTable,
|
|
769
|
+
spawnerKey,
|
|
770
|
+
psParent,
|
|
771
|
+
harnessPid,
|
|
772
|
+
hookHarnessPid,
|
|
773
|
+
relayIdentity,
|
|
774
|
+
DEFAULT_RELAY,
|
|
775
|
+
configDir,
|
|
776
|
+
checkRelayUrl,
|
|
777
|
+
writePrivateJson,
|
|
778
|
+
activeRelay,
|
|
779
|
+
joinMode,
|
|
780
|
+
setJoinMode,
|
|
781
|
+
readCredentials,
|
|
782
|
+
saveCredential,
|
|
783
|
+
removeCredential,
|
|
784
|
+
relayCredential,
|
|
785
|
+
relayVerb,
|
|
786
|
+
loginMain,
|
|
787
|
+
logoutMain,
|
|
788
|
+
membership,
|
|
789
|
+
RelayUsage,
|
|
790
|
+
OPTIONS,
|
|
791
|
+
SPAWN_OPTIONS,
|
|
792
|
+
startDaemon,
|
|
793
|
+
localHandle,
|
|
794
|
+
walkNow,
|
|
795
|
+
open,
|
|
796
|
+
localClient,
|
|
797
|
+
brokerLine,
|
|
798
|
+
selectorList,
|
|
799
|
+
preset,
|
|
800
|
+
peekGrant,
|
|
801
|
+
grantParams,
|
|
802
|
+
time,
|
|
803
|
+
stamp,
|
|
804
|
+
CHANNELS_ENV
|
|
805
|
+
};
|