@craftspace/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/dist/index.js +76 -26
- package/dist/machine.js +24 -2
- package/dist/run.js +22 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19258,6 +19258,12 @@ var CreateConnectionBodySchema = external_exports.object({
|
|
|
19258
19258
|
// sent it, and can start a connect for that one kit and nothing else.
|
|
19259
19259
|
inviteToken: external_exports.string().optional()
|
|
19260
19260
|
});
|
|
19261
|
+
var ReplaceConnectionSecretBodySchema = external_exports.object({
|
|
19262
|
+
mcpUrl: external_exports.url().optional(),
|
|
19263
|
+
authToken: external_exports.string().min(1).optional(),
|
|
19264
|
+
dsn: external_exports.string().min(1).optional(),
|
|
19265
|
+
apiKey: external_exports.string().min(1).optional()
|
|
19266
|
+
});
|
|
19261
19267
|
var ConnectionIdParamsSchema = external_exports.object({ id: external_exports.string() });
|
|
19262
19268
|
var PendingOAuthSchema = external_exports.object({
|
|
19263
19269
|
id: external_exports.string(),
|
|
@@ -20221,7 +20227,7 @@ var MemberActivityListResponse = external_exports.object({
|
|
|
20221
20227
|
// dist/machine.js
|
|
20222
20228
|
import { execFile as execFile3 } from "node:child_process";
|
|
20223
20229
|
import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "node:fs/promises";
|
|
20224
|
-
import
|
|
20230
|
+
import os4 from "node:os";
|
|
20225
20231
|
import path4 from "node:path";
|
|
20226
20232
|
import { promisify as promisify3 } from "node:util";
|
|
20227
20233
|
|
|
@@ -22091,6 +22097,7 @@ var dist_default6 = createPrompt((config2, done) => {
|
|
|
22091
22097
|
|
|
22092
22098
|
// dist/run.js
|
|
22093
22099
|
import { spawn } from "node:child_process";
|
|
22100
|
+
import os from "node:os";
|
|
22094
22101
|
var runner = {
|
|
22095
22102
|
attended(argv) {
|
|
22096
22103
|
return new Promise((resolve) => {
|
|
@@ -22101,6 +22108,7 @@ var runner = {
|
|
|
22101
22108
|
},
|
|
22102
22109
|
async unattended({ work, onProgress }) {
|
|
22103
22110
|
const argv = headlessArgv(work);
|
|
22111
|
+
const raw = argv[0] === "script";
|
|
22104
22112
|
let output = "";
|
|
22105
22113
|
let agentSessionId = work.agentSessionId ?? void 0;
|
|
22106
22114
|
let lastText = "";
|
|
@@ -22126,10 +22134,24 @@ var runner = {
|
|
|
22126
22134
|
}
|
|
22127
22135
|
};
|
|
22128
22136
|
const code = await new Promise((resolve) => {
|
|
22129
|
-
const child = spawn(argv[0], argv.slice(1), {
|
|
22137
|
+
const child = spawn(argv[0], argv.slice(1), {
|
|
22138
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
22139
|
+
cwd: os.homedir(),
|
|
22140
|
+
env: {
|
|
22141
|
+
...process.env,
|
|
22142
|
+
TERM: process.env.TERM ?? "xterm-256color",
|
|
22143
|
+
COLUMNS: process.env.COLUMNS ?? "120",
|
|
22144
|
+
LINES: process.env.LINES ?? "40"
|
|
22145
|
+
}
|
|
22146
|
+
});
|
|
22147
|
+
append("");
|
|
22130
22148
|
child.stdout.setEncoding("utf8");
|
|
22131
22149
|
child.stderr.setEncoding("utf8");
|
|
22132
22150
|
child.stdout.on("data", (chunk) => {
|
|
22151
|
+
if (raw) {
|
|
22152
|
+
append(chunk);
|
|
22153
|
+
return;
|
|
22154
|
+
}
|
|
22133
22155
|
pending += chunk;
|
|
22134
22156
|
const lines = pending.split("\n");
|
|
22135
22157
|
pending = lines.pop() ?? "";
|
|
@@ -22179,8 +22201,11 @@ function headlessArgv(work) {
|
|
|
22179
22201
|
return [argv[0], "-p", argv.slice(1).join(" "), ...streaming];
|
|
22180
22202
|
}
|
|
22181
22203
|
function underTty(argv) {
|
|
22182
|
-
|
|
22204
|
+
const line = `stty rows ${PTY_ROWS} cols ${PTY_COLS} 2>/dev/null; exec ${argv.join(" ")}`;
|
|
22205
|
+
return process.platform === "linux" ? ["script", "-qec", line, "/dev/null"] : ["script", "-q", "/dev/null", "sh", "-c", line];
|
|
22183
22206
|
}
|
|
22207
|
+
var PTY_ROWS = 40;
|
|
22208
|
+
var PTY_COLS = 120;
|
|
22184
22209
|
function isAgent(command) {
|
|
22185
22210
|
return AGENTS.has((command ?? "").split("/").pop() ?? "");
|
|
22186
22211
|
}
|
|
@@ -22209,7 +22234,7 @@ function parseJson(line) {
|
|
|
22209
22234
|
|
|
22210
22235
|
// dist/setup.js
|
|
22211
22236
|
import { execFile as execFile2 } from "node:child_process";
|
|
22212
|
-
import
|
|
22237
|
+
import os3 from "node:os";
|
|
22213
22238
|
import path3 from "node:path";
|
|
22214
22239
|
import { promisify as promisify2 } from "node:util";
|
|
22215
22240
|
|
|
@@ -22217,12 +22242,12 @@ import { promisify as promisify2 } from "node:util";
|
|
|
22217
22242
|
import { execFile } from "node:child_process";
|
|
22218
22243
|
import net from "node:net";
|
|
22219
22244
|
import { access, readFile } from "node:fs/promises";
|
|
22220
|
-
import
|
|
22245
|
+
import os2 from "node:os";
|
|
22221
22246
|
import path2 from "node:path";
|
|
22222
22247
|
import { promisify } from "node:util";
|
|
22223
22248
|
var run = promisify(execFile);
|
|
22224
22249
|
var probe = {
|
|
22225
|
-
async tools({ mcpPath = path2.join(
|
|
22250
|
+
async tools({ mcpPath = path2.join(os2.homedir(), ".mcp.json") } = {}) {
|
|
22226
22251
|
const [brain, claude, codex, git, chrome, ssh] = await Promise.all([
|
|
22227
22252
|
brainWired(mcpPath),
|
|
22228
22253
|
version2({ id: "claude", command: "claude" }),
|
|
@@ -22268,7 +22293,7 @@ async function gitAndGithub() {
|
|
|
22268
22293
|
};
|
|
22269
22294
|
}
|
|
22270
22295
|
async function githubAccount() {
|
|
22271
|
-
const raw = await readFile(path2.join(
|
|
22296
|
+
const raw = await readFile(path2.join(os2.homedir(), ".config", "gh", "hosts.yml"), "utf8").catch(() => null);
|
|
22272
22297
|
return raw === null ? null : githubUser(raw);
|
|
22273
22298
|
}
|
|
22274
22299
|
function githubUser(hostsYml) {
|
|
@@ -22308,7 +22333,7 @@ async function firstLine(command, args) {
|
|
|
22308
22333
|
return line === "" ? { ran: false, why: "broken" } : { ran: true, line: line.slice(0, 200) };
|
|
22309
22334
|
}
|
|
22310
22335
|
function short(target) {
|
|
22311
|
-
return target.startsWith(
|
|
22336
|
+
return target.startsWith(os2.homedir()) ? `~${target.slice(os2.homedir().length)}` : target;
|
|
22312
22337
|
}
|
|
22313
22338
|
var MAC_CHROME = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
|
|
22314
22339
|
var LINUX_CHROME = ["google-chrome", "google-chrome-stable", "chromium"];
|
|
@@ -22363,8 +22388,8 @@ function toolDirs() {
|
|
|
22363
22388
|
return [
|
|
22364
22389
|
path3.dirname(process.execPath),
|
|
22365
22390
|
...prefix === void 0 ? [] : [path3.join(prefix, "bin")],
|
|
22366
|
-
path3.join(
|
|
22367
|
-
path3.join(
|
|
22391
|
+
path3.join(os3.homedir(), ".local", "bin"),
|
|
22392
|
+
path3.join(os3.homedir(), ".npm-global", "bin"),
|
|
22368
22393
|
"/opt/homebrew/bin",
|
|
22369
22394
|
"/usr/local/bin"
|
|
22370
22395
|
];
|
|
@@ -22419,7 +22444,7 @@ var RETRY_AFTER_MS = 216e5;
|
|
|
22419
22444
|
var run3 = promisify3(execFile3);
|
|
22420
22445
|
var machine = {
|
|
22421
22446
|
home() {
|
|
22422
|
-
return process.env.CRAFTSPACE_CLI_HOME ?? path4.join(
|
|
22447
|
+
return process.env.CRAFTSPACE_CLI_HOME ?? path4.join(os4.homedir(), ".craftspace");
|
|
22423
22448
|
},
|
|
22424
22449
|
async login({ token, url: url2, install }) {
|
|
22425
22450
|
const answer = await call({
|
|
@@ -22428,11 +22453,11 @@ var machine = {
|
|
|
22428
22453
|
method: "POST",
|
|
22429
22454
|
path: "/api/machines/login",
|
|
22430
22455
|
body: {
|
|
22431
|
-
name:
|
|
22456
|
+
name: os4.hostname().split(".")[0] ?? "machine",
|
|
22432
22457
|
platform: `${process.platform}-${process.arch}`,
|
|
22433
|
-
sshUser:
|
|
22434
|
-
cores:
|
|
22435
|
-
memoryGb: Math.max(1, Math.round(
|
|
22458
|
+
sshUser: os4.userInfo().username,
|
|
22459
|
+
cores: os4.cpus().length,
|
|
22460
|
+
memoryGb: Math.max(1, Math.round(os4.totalmem() / 1024 ** 3)),
|
|
22436
22461
|
cli: CLI_VERSION,
|
|
22437
22462
|
publicKey: await ensureKey()
|
|
22438
22463
|
},
|
|
@@ -22495,7 +22520,7 @@ Signed in as ${signed.ownerName}. This machine is ${signed.name}.
|
|
|
22495
22520
|
sessions: await readSessions(),
|
|
22496
22521
|
runs: drainReports(),
|
|
22497
22522
|
tools: await tools(),
|
|
22498
|
-
sshUser:
|
|
22523
|
+
sshUser: os4.userInfo().username
|
|
22499
22524
|
},
|
|
22500
22525
|
schema: MachineBeatResponseSchema
|
|
22501
22526
|
});
|
|
@@ -22736,7 +22761,7 @@ async function ensureKey() {
|
|
|
22736
22761
|
if (existing !== null)
|
|
22737
22762
|
return existing.trim();
|
|
22738
22763
|
await mkdir(machine.home(), { recursive: true, mode: 448 });
|
|
22739
|
-
await run3("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${
|
|
22764
|
+
await run3("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os4.hostname()}`]);
|
|
22740
22765
|
return (await readFile2(pub, "utf8")).trim();
|
|
22741
22766
|
}
|
|
22742
22767
|
async function readConfig() {
|
|
@@ -22764,7 +22789,7 @@ async function readSessions() {
|
|
|
22764
22789
|
return sessions.slice(0, MAX_REPORTED_SESSIONS);
|
|
22765
22790
|
}
|
|
22766
22791
|
async function writeAuthorizedKeys(keys) {
|
|
22767
|
-
const target = path4.join(
|
|
22792
|
+
const target = path4.join(os4.homedir(), ".ssh", "authorized_keys");
|
|
22768
22793
|
const current = await readFile2(target, "utf8").catch(() => "");
|
|
22769
22794
|
const before = current.split(KEYS_BEGIN)[0] ?? "";
|
|
22770
22795
|
const after = current.includes(KEYS_END) ? current.split(KEYS_END)[1] ?? "" : "";
|
|
@@ -22804,6 +22829,31 @@ async function writeAgentConfigs({ url: url2, token, writes }) {
|
|
|
22804
22829
|
owned.push(target);
|
|
22805
22830
|
}
|
|
22806
22831
|
await writeFile(ownedPath(), JSON.stringify({ paths: owned }, null, 2));
|
|
22832
|
+
await acceptClaudeGates(serversIn(planned));
|
|
22833
|
+
}
|
|
22834
|
+
function serversIn(writes) {
|
|
22835
|
+
return writes.flatMap((write) => isPlainObject3(write.contents.mcpServers) ? Object.keys(write.contents.mcpServers) : []);
|
|
22836
|
+
}
|
|
22837
|
+
async function acceptClaudeGates(servers) {
|
|
22838
|
+
const target = path4.join(os4.homedir(), ".claude.json");
|
|
22839
|
+
const raw = await readFile2(target, "utf8").catch(() => null);
|
|
22840
|
+
if (raw === null)
|
|
22841
|
+
return;
|
|
22842
|
+
const parsed = JSON.parse(raw);
|
|
22843
|
+
const projects = isPlainObject3(parsed.projects) ? parsed.projects : {};
|
|
22844
|
+
const found = projects[os4.homedir()];
|
|
22845
|
+
const here = isPlainObject3(found) ? { ...found } : {};
|
|
22846
|
+
const enabled = /* @__PURE__ */ new Set([...Array.isArray(here.enabledMcpjsonServers) ? here.enabledMcpjsonServers : [], ...servers]);
|
|
22847
|
+
if (here.hasTrustDialogAccepted === true && enabled.size === asArray(here.enabledMcpjsonServers).length)
|
|
22848
|
+
return;
|
|
22849
|
+
here.hasTrustDialogAccepted = true;
|
|
22850
|
+
here.enabledMcpjsonServers = [...enabled];
|
|
22851
|
+
const next = { ...parsed, projects: { ...projects, [os4.homedir()]: here } };
|
|
22852
|
+
await writeFile(target, `${JSON.stringify(next, null, 2)}
|
|
22853
|
+
`, { mode: 384 });
|
|
22854
|
+
}
|
|
22855
|
+
function asArray(value) {
|
|
22856
|
+
return Array.isArray(value) ? value : [];
|
|
22807
22857
|
}
|
|
22808
22858
|
function mergeInto(current, incoming) {
|
|
22809
22859
|
const merged = { ...current };
|
|
@@ -22851,7 +22901,7 @@ async function installService() {
|
|
|
22851
22901
|
return;
|
|
22852
22902
|
}
|
|
22853
22903
|
if (process.platform === "linux") {
|
|
22854
|
-
const dir = path4.join(
|
|
22904
|
+
const dir = path4.join(os4.homedir(), ".config", "systemd", "user");
|
|
22855
22905
|
await mkdir(dir, { recursive: true });
|
|
22856
22906
|
await writeFile(path4.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
|
|
22857
22907
|
await run3("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
|
|
@@ -22859,7 +22909,7 @@ async function installService() {
|
|
|
22859
22909
|
return;
|
|
22860
22910
|
}
|
|
22861
22911
|
if (process.platform === "darwin") {
|
|
22862
|
-
const target = path4.join(
|
|
22912
|
+
const target = path4.join(os4.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
|
|
22863
22913
|
await mkdir(path4.dirname(target), { recursive: true });
|
|
22864
22914
|
await writeFile(target, launchdPlist());
|
|
22865
22915
|
await run3("launchctl", ["unload", target]).catch(() => void 0);
|
|
@@ -22869,11 +22919,11 @@ async function installService() {
|
|
|
22869
22919
|
async function uninstallService() {
|
|
22870
22920
|
if (process.platform === "linux") {
|
|
22871
22921
|
await run3("systemctl", ["--user", "disable", "--now", SERVICE_NAME]).catch(() => void 0);
|
|
22872
|
-
await rm(path4.join(
|
|
22922
|
+
await rm(path4.join(os4.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`), { force: true });
|
|
22873
22923
|
return;
|
|
22874
22924
|
}
|
|
22875
22925
|
if (process.platform === "darwin") {
|
|
22876
|
-
const target = path4.join(
|
|
22926
|
+
const target = path4.join(os4.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
|
|
22877
22927
|
await run3("launchctl", ["unload", target]).catch(() => void 0);
|
|
22878
22928
|
await rm(target, { force: true });
|
|
22879
22929
|
}
|
|
@@ -22911,7 +22961,7 @@ function entryPath() {
|
|
|
22911
22961
|
return path4.join(path4.dirname(new URL(import.meta.url).pathname), "index.js");
|
|
22912
22962
|
}
|
|
22913
22963
|
function expand(target) {
|
|
22914
|
-
return target.startsWith("~/") ? path4.join(
|
|
22964
|
+
return target.startsWith("~/") ? path4.join(os4.homedir(), target.slice(2)) : target;
|
|
22915
22965
|
}
|
|
22916
22966
|
function configPath() {
|
|
22917
22967
|
return path4.join(machine.home(), "config.json");
|
|
@@ -22923,9 +22973,9 @@ function ownedPath() {
|
|
|
22923
22973
|
return path4.join(machine.home(), "owned.json");
|
|
22924
22974
|
}
|
|
22925
22975
|
function load2() {
|
|
22926
|
-
const cores = Math.max(1,
|
|
22927
|
-
const busy = Math.min(100, (
|
|
22928
|
-
const used = (
|
|
22976
|
+
const cores = Math.max(1, os4.cpus().length);
|
|
22977
|
+
const busy = Math.min(100, (os4.loadavg()[0] ?? 0) / cores * 100);
|
|
22978
|
+
const used = (os4.totalmem() - os4.freemem()) / os4.totalmem() * 100;
|
|
22929
22979
|
return { cpuPercent: Math.round(busy), memoryPercent: Math.round(used) };
|
|
22930
22980
|
}
|
|
22931
22981
|
function nextDelayMs(failures, busy = false) {
|
package/dist/machine.js
CHANGED
|
@@ -348,8 +348,6 @@ async function readSessions() {
|
|
|
348
348
|
}
|
|
349
349
|
return sessions.slice(0, MAX_REPORTED_SESSIONS);
|
|
350
350
|
}
|
|
351
|
-
// Only the block between our markers is ours. Everything a person put in this file by hand is left exactly
|
|
352
|
-
// where it was, because the fastest way to lock a team out of its own box is to rewrite this file wholesale.
|
|
353
351
|
async function writeAuthorizedKeys(keys) {
|
|
354
352
|
const target = path.join(os.homedir(), '.ssh', 'authorized_keys');
|
|
355
353
|
const current = await readFile(target, 'utf8').catch(() => '');
|
|
@@ -387,6 +385,30 @@ async function writeAgentConfigs({ url, token, writes, }) {
|
|
|
387
385
|
owned.push(target);
|
|
388
386
|
}
|
|
389
387
|
await writeFile(ownedPath(), JSON.stringify({ paths: owned }, null, 2));
|
|
388
|
+
await acceptClaudeGates(serversIn(planned));
|
|
389
|
+
}
|
|
390
|
+
function serversIn(writes) {
|
|
391
|
+
return writes.flatMap((write) => isPlainObject(write.contents.mcpServers) ? Object.keys(write.contents.mcpServers) : []);
|
|
392
|
+
}
|
|
393
|
+
async function acceptClaudeGates(servers) {
|
|
394
|
+
const target = path.join(os.homedir(), '.claude.json');
|
|
395
|
+
const raw = await readFile(target, 'utf8').catch(() => null);
|
|
396
|
+
if (raw === null)
|
|
397
|
+
return;
|
|
398
|
+
const parsed = JSON.parse(raw);
|
|
399
|
+
const projects = isPlainObject(parsed.projects) ? parsed.projects : {};
|
|
400
|
+
const found = projects[os.homedir()];
|
|
401
|
+
const here = isPlainObject(found) ? { ...found } : {};
|
|
402
|
+
const enabled = new Set([...(Array.isArray(here.enabledMcpjsonServers) ? here.enabledMcpjsonServers : []), ...servers]);
|
|
403
|
+
if (here.hasTrustDialogAccepted === true && enabled.size === asArray(here.enabledMcpjsonServers).length)
|
|
404
|
+
return;
|
|
405
|
+
here.hasTrustDialogAccepted = true;
|
|
406
|
+
here.enabledMcpjsonServers = [...enabled];
|
|
407
|
+
const next = { ...parsed, projects: { ...projects, [os.homedir()]: here } };
|
|
408
|
+
await writeFile(target, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
409
|
+
}
|
|
410
|
+
function asArray(value) {
|
|
411
|
+
return Array.isArray(value) ? value : [];
|
|
390
412
|
}
|
|
391
413
|
function mergeInto(current, incoming) {
|
|
392
414
|
const merged = { ...current };
|
package/dist/run.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import { MACHINE_RUN_OUTPUT_MAX, spreadIfDefined } from '@craftspace/shared';
|
|
3
4
|
export const runner = {
|
|
4
5
|
attended(argv) {
|
|
@@ -10,6 +11,7 @@ export const runner = {
|
|
|
10
11
|
},
|
|
11
12
|
async unattended({ work, onProgress, }) {
|
|
12
13
|
const argv = headlessArgv(work);
|
|
14
|
+
const raw = argv[0] === 'script';
|
|
13
15
|
let output = '';
|
|
14
16
|
let agentSessionId = work.agentSessionId ?? undefined;
|
|
15
17
|
let lastText = '';
|
|
@@ -33,10 +35,24 @@ export const runner = {
|
|
|
33
35
|
}
|
|
34
36
|
};
|
|
35
37
|
const code = await new Promise((resolve) => {
|
|
36
|
-
const child = spawn(argv[0], argv.slice(1), {
|
|
38
|
+
const child = spawn(argv[0], argv.slice(1), {
|
|
39
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
40
|
+
cwd: os.homedir(),
|
|
41
|
+
env: {
|
|
42
|
+
...process.env,
|
|
43
|
+
TERM: process.env.TERM ?? 'xterm-256color',
|
|
44
|
+
COLUMNS: process.env.COLUMNS ?? '120',
|
|
45
|
+
LINES: process.env.LINES ?? '40',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
append('');
|
|
37
49
|
child.stdout.setEncoding('utf8');
|
|
38
50
|
child.stderr.setEncoding('utf8');
|
|
39
51
|
child.stdout.on('data', (chunk) => {
|
|
52
|
+
if (raw) {
|
|
53
|
+
append(chunk);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
40
56
|
pending += chunk;
|
|
41
57
|
const lines = pending.split('\n');
|
|
42
58
|
pending = lines.pop() ?? '';
|
|
@@ -85,10 +101,13 @@ export function headlessArgv(work) {
|
|
|
85
101
|
return [argv[0], '-p', argv.slice(1).join(' '), ...streaming];
|
|
86
102
|
}
|
|
87
103
|
function underTty(argv) {
|
|
104
|
+
const line = `stty rows ${PTY_ROWS} cols ${PTY_COLS} 2>/dev/null; exec ${argv.join(' ')}`;
|
|
88
105
|
return process.platform === 'linux'
|
|
89
|
-
? ['script', '-qec',
|
|
90
|
-
: ['script', '-q', '/dev/null',
|
|
106
|
+
? ['script', '-qec', line, '/dev/null']
|
|
107
|
+
: ['script', '-q', '/dev/null', 'sh', '-c', line];
|
|
91
108
|
}
|
|
109
|
+
const PTY_ROWS = 40;
|
|
110
|
+
const PTY_COLS = 120;
|
|
92
111
|
function isAgent(command) {
|
|
93
112
|
return AGENTS.has((command ?? '').split('/').pop() ?? '');
|
|
94
113
|
}
|