@telepath-computer/television 1.4.7 → 1.4.8
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/cli.cjs
CHANGED
|
@@ -34472,12 +34472,14 @@ var import_node_url = require("node:url");
|
|
|
34472
34472
|
|
|
34473
34473
|
// ../server/src/config.ts
|
|
34474
34474
|
var import_node_fs2 = require("node:fs");
|
|
34475
|
+
var import_node_net = require("node:net");
|
|
34475
34476
|
var import_node_os2 = __toESM(require("node:os"), 1);
|
|
34476
34477
|
var import_node_path2 = __toESM(require("node:path"), 1);
|
|
34477
34478
|
var TRUE_ENV_VALUE = "true";
|
|
34478
34479
|
var APP_HIDDEN_DIRECTORY = ".television";
|
|
34479
|
-
var
|
|
34480
|
-
var
|
|
34480
|
+
var HOME_POINTER_FILE = ".tv-home";
|
|
34481
|
+
var CONFIG_FILE = "config.json";
|
|
34482
|
+
var CONFIG_KEYS = ["port", "listen", "auth", "installedByAgent"];
|
|
34481
34483
|
var TELEVISION_ACP_AGENT_ENV = "TELEVISION_ACP_AGENT";
|
|
34482
34484
|
var TELEVISION_DEVELOPER_HOME_ENV = "TELEVISION_DEVELOPER_HOME";
|
|
34483
34485
|
var MAX_PORT = 65535;
|
|
@@ -34513,25 +34515,153 @@ var DEFAULT_SERVER_URL = buildServerURL(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT
|
|
|
34513
34515
|
function buildServerURL(host, port) {
|
|
34514
34516
|
return `http://${host}:${port}`;
|
|
34515
34517
|
}
|
|
34516
|
-
|
|
34517
|
-
|
|
34518
|
+
var TelevisionConfigError = class extends Error {
|
|
34519
|
+
configPath;
|
|
34520
|
+
constructor(configPath, message) {
|
|
34521
|
+
super(message);
|
|
34522
|
+
this.name = "TelevisionConfigError";
|
|
34523
|
+
this.configPath = configPath;
|
|
34524
|
+
}
|
|
34525
|
+
};
|
|
34526
|
+
function resolveTelevisionHome(input) {
|
|
34527
|
+
const { homeOption, operatingSystemHomeDir, cwd } = input;
|
|
34528
|
+
if (homeOption !== void 0) {
|
|
34529
|
+
if (homeOption.trim() === "") {
|
|
34530
|
+
throw new Error("--home requires a path; an empty value does not select the default home.");
|
|
34531
|
+
}
|
|
34532
|
+
return import_node_path2.default.resolve(cwd, homeOption);
|
|
34533
|
+
}
|
|
34534
|
+
const pointerPath = import_node_path2.default.join(operatingSystemHomeDir, HOME_POINTER_FILE);
|
|
34535
|
+
const builtInHome = import_node_path2.default.join(operatingSystemHomeDir, APP_HIDDEN_DIRECTORY);
|
|
34536
|
+
let contents;
|
|
34537
|
+
try {
|
|
34538
|
+
contents = (0, import_node_fs2.readFileSync)(pointerPath, "utf8");
|
|
34539
|
+
} catch (error48) {
|
|
34540
|
+
if (isMissingFileError(error48)) return builtInHome;
|
|
34541
|
+
throw new Error(`${pointerPath} cannot be read: ${errorMessage(error48)}`);
|
|
34542
|
+
}
|
|
34543
|
+
const pointer = contents.trim();
|
|
34544
|
+
if (pointer === "") {
|
|
34545
|
+
throw new Error(`${pointerPath} holds no path. Write the Television home's path in it, or delete it to use ${builtInHome}.`);
|
|
34546
|
+
}
|
|
34547
|
+
if (/[\r\n]/.test(pointer)) {
|
|
34548
|
+
throw new Error(`${pointerPath} holds more than one line. It must hold only the Television home's path.`);
|
|
34549
|
+
}
|
|
34550
|
+
if (pointer === "~" || pointer.startsWith("~/")) {
|
|
34551
|
+
return import_node_path2.default.resolve(import_node_path2.default.join(operatingSystemHomeDir, pointer.slice(1)));
|
|
34552
|
+
}
|
|
34553
|
+
if (pointer.startsWith("~")) {
|
|
34554
|
+
throw new Error(`${pointerPath} holds ${pointer}, but ~ followed by a user name is not supported. Use an absolute path instead.`);
|
|
34555
|
+
}
|
|
34556
|
+
return import_node_path2.default.resolve(operatingSystemHomeDir, pointer);
|
|
34518
34557
|
}
|
|
34519
|
-
function
|
|
34520
|
-
|
|
34558
|
+
function readTelevisionConfig(home) {
|
|
34559
|
+
const configPath = getConfigPath(home);
|
|
34560
|
+
const stored = readStoredConfig(configPath);
|
|
34561
|
+
if (stored === void 0) return buildConfig(configPath, false, {});
|
|
34562
|
+
return buildConfig(configPath, true, validateConfigFile(configPath, stored));
|
|
34521
34563
|
}
|
|
34522
|
-
function
|
|
34523
|
-
const
|
|
34524
|
-
|
|
34525
|
-
|
|
34564
|
+
function updateTelevisionConfig(home, changes) {
|
|
34565
|
+
const configPath = getConfigPath(home);
|
|
34566
|
+
const merged = { ...readStoredConfig(configPath) };
|
|
34567
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
34568
|
+
if (value !== void 0) merged[key] = value;
|
|
34526
34569
|
}
|
|
34527
|
-
|
|
34528
|
-
|
|
34570
|
+
const file2 = validateConfigFile(configPath, merged);
|
|
34571
|
+
const resolvedHome = import_node_path2.default.dirname(configPath);
|
|
34572
|
+
(0, import_node_fs2.mkdirSync)(resolvedHome, { recursive: true });
|
|
34573
|
+
const temporaryPath = import_node_path2.default.join(resolvedHome, `.${CONFIG_FILE}.${process.pid}.${Date.now()}.tmp`);
|
|
34574
|
+
try {
|
|
34575
|
+
(0, import_node_fs2.writeFileSync)(temporaryPath, `${JSON.stringify(merged, null, 2)}
|
|
34576
|
+
`, { flag: "wx" });
|
|
34577
|
+
(0, import_node_fs2.renameSync)(temporaryPath, configPath);
|
|
34578
|
+
} catch (error48) {
|
|
34579
|
+
(0, import_node_fs2.rmSync)(temporaryPath, { force: true });
|
|
34580
|
+
throw error48;
|
|
34529
34581
|
}
|
|
34530
|
-
|
|
34531
|
-
|
|
34532
|
-
|
|
34582
|
+
return buildConfig(configPath, true, file2);
|
|
34583
|
+
}
|
|
34584
|
+
function getConfigPath(home) {
|
|
34585
|
+
return import_node_path2.default.join(import_node_path2.default.resolve(home), CONFIG_FILE);
|
|
34586
|
+
}
|
|
34587
|
+
function readStoredConfig(configPath) {
|
|
34588
|
+
let contents;
|
|
34589
|
+
try {
|
|
34590
|
+
contents = (0, import_node_fs2.readFileSync)(configPath, "utf8");
|
|
34591
|
+
} catch (error48) {
|
|
34592
|
+
if (isMissingFileError(error48)) return void 0;
|
|
34593
|
+
throw new TelevisionConfigError(configPath, `Config file ${configPath} cannot be read: ${errorMessage(error48)}`);
|
|
34533
34594
|
}
|
|
34534
|
-
|
|
34595
|
+
let parsed;
|
|
34596
|
+
try {
|
|
34597
|
+
parsed = JSON.parse(contents);
|
|
34598
|
+
} catch (error48) {
|
|
34599
|
+
throw new TelevisionConfigError(configPath, `Config file ${configPath} is not valid JSON: ${errorMessage(error48)}`);
|
|
34600
|
+
}
|
|
34601
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
34602
|
+
const found = Array.isArray(parsed) ? "an array" : parsed === null ? "null" : `a ${typeof parsed}`;
|
|
34603
|
+
throw new TelevisionConfigError(configPath, `Config file ${configPath} must hold a JSON object, not ${found}.`);
|
|
34604
|
+
}
|
|
34605
|
+
return parsed;
|
|
34606
|
+
}
|
|
34607
|
+
function validateConfigFile(configPath, stored) {
|
|
34608
|
+
const invalid = (problem) => new TelevisionConfigError(configPath, `Config file ${configPath}: ${problem}`);
|
|
34609
|
+
const file2 = {};
|
|
34610
|
+
for (const [key, value] of Object.entries(stored)) {
|
|
34611
|
+
const found = `got ${JSON.stringify(value)}`;
|
|
34612
|
+
switch (key) {
|
|
34613
|
+
case "port":
|
|
34614
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > MAX_PORT) {
|
|
34615
|
+
throw invalid(`"port" must be a whole number from 0 through ${MAX_PORT}; ${found}.`);
|
|
34616
|
+
}
|
|
34617
|
+
file2.port = value;
|
|
34618
|
+
break;
|
|
34619
|
+
case "listen":
|
|
34620
|
+
if (!Array.isArray(value)) {
|
|
34621
|
+
throw invalid(`"listen" must be an array of IPv4 address strings; ${found}.`);
|
|
34622
|
+
}
|
|
34623
|
+
for (const item of value) {
|
|
34624
|
+
if (typeof item !== "string" || !(0, import_node_net.isIPv4)(item)) {
|
|
34625
|
+
throw invalid(`each "listen" item must be an IPv4 address string; got ${JSON.stringify(item)}.`);
|
|
34626
|
+
}
|
|
34627
|
+
}
|
|
34628
|
+
file2.listen = [...value];
|
|
34629
|
+
break;
|
|
34630
|
+
case "auth":
|
|
34631
|
+
if (typeof value !== "boolean") {
|
|
34632
|
+
throw invalid(`"auth" must be true or false; ${found}.`);
|
|
34633
|
+
}
|
|
34634
|
+
file2.auth = value;
|
|
34635
|
+
break;
|
|
34636
|
+
case "installedByAgent":
|
|
34637
|
+
if (typeof value !== "string") {
|
|
34638
|
+
throw invalid(`"installedByAgent" must be a string; ${found}.`);
|
|
34639
|
+
}
|
|
34640
|
+
file2.installedByAgent = value;
|
|
34641
|
+
break;
|
|
34642
|
+
default:
|
|
34643
|
+
throw invalid(`unknown key ${JSON.stringify(key)}. Valid keys are ${CONFIG_KEYS.join(", ")}.`);
|
|
34644
|
+
}
|
|
34645
|
+
}
|
|
34646
|
+
return file2;
|
|
34647
|
+
}
|
|
34648
|
+
function buildConfig(configPath, configFileExists, file2) {
|
|
34649
|
+
const settings = {
|
|
34650
|
+
port: file2.port ?? DEFAULT_SERVER_PORT,
|
|
34651
|
+
listen: file2.listen ?? [],
|
|
34652
|
+
auth: file2.auth ?? true
|
|
34653
|
+
};
|
|
34654
|
+
if (file2.installedByAgent !== void 0) settings.installedByAgent = file2.installedByAgent;
|
|
34655
|
+
return { home: import_node_path2.default.dirname(configPath), configPath, configFileExists, settings };
|
|
34656
|
+
}
|
|
34657
|
+
function isMissingFileError(error48) {
|
|
34658
|
+
return error48?.code === "ENOENT";
|
|
34659
|
+
}
|
|
34660
|
+
function errorMessage(error48) {
|
|
34661
|
+
return error48 instanceof Error ? error48.message : String(error48);
|
|
34662
|
+
}
|
|
34663
|
+
function getDefaultTelevisionStoragePath() {
|
|
34664
|
+
return import_node_path2.default.join(import_node_os2.default.homedir(), APP_HIDDEN_DIRECTORY);
|
|
34535
34665
|
}
|
|
34536
34666
|
function resolveACPAgentProfile(env = process.env) {
|
|
34537
34667
|
const rawAgent = env[TELEVISION_ACP_AGENT_ENV];
|
|
@@ -48942,7 +49072,7 @@ function isNewerVersion(candidate, baseline) {
|
|
|
48942
49072
|
}
|
|
48943
49073
|
|
|
48944
49074
|
// ../server/src/telemetry/derivation.ts
|
|
48945
|
-
var
|
|
49075
|
+
var import_node_net2 = require("node:net");
|
|
48946
49076
|
|
|
48947
49077
|
// ../../node_modules/ulid/dist/node/index.js
|
|
48948
49078
|
var import_node_crypto = __toESM(require("node:crypto"), 1);
|
|
@@ -49943,7 +50073,7 @@ function isLoopbackAddress(address) {
|
|
|
49943
50073
|
return normalized === "localhost" || normalized === "::1" || normalized === "[::1]" || normalized.startsWith("127.");
|
|
49944
50074
|
}
|
|
49945
50075
|
function isTailnetIPv4(hostname3) {
|
|
49946
|
-
if ((0,
|
|
50076
|
+
if ((0, import_node_net2.isIP)(hostname3) !== IPV4_VERSION) return false;
|
|
49947
50077
|
const [a, b2] = hostname3.split(".").map((part) => Number.parseInt(part, 10));
|
|
49948
50078
|
return a === TAILNET_FIRST_OCTET && b2 !== void 0 && b2 >= TAILNET_MIN_SECOND_OCTET && b2 <= TAILNET_MAX_SECOND_OCTET;
|
|
49949
50079
|
}
|
|
@@ -52744,6 +52874,11 @@ function trackChild(child) {
|
|
|
52744
52874
|
child.once("error", untrack);
|
|
52745
52875
|
}
|
|
52746
52876
|
var ACP_STUB_CWD = import_node_path8.default.join((0, import_node_os4.homedir)(), ".television", "acp");
|
|
52877
|
+
function buildACPAgentTVArgs(input) {
|
|
52878
|
+
const args = ["--home", import_node_path8.default.resolve(input.home)];
|
|
52879
|
+
if (input.configuredPort === 0) args.push("--port", String(input.acquiredPort));
|
|
52880
|
+
return args;
|
|
52881
|
+
}
|
|
52747
52882
|
var ACPBridge = class extends withDisposable(class {
|
|
52748
52883
|
}) {
|
|
52749
52884
|
child = null;
|
|
@@ -52753,12 +52888,14 @@ var ACPBridge = class extends withDisposable(class {
|
|
|
52753
52888
|
stderrBuffer = "";
|
|
52754
52889
|
send;
|
|
52755
52890
|
profile;
|
|
52891
|
+
tvArgs;
|
|
52756
52892
|
launchProcess;
|
|
52757
52893
|
sessionCwd;
|
|
52758
52894
|
constructor(options) {
|
|
52759
52895
|
super();
|
|
52760
52896
|
this.send = options.send;
|
|
52761
52897
|
this.profile = options.profile;
|
|
52898
|
+
this.tvArgs = options.tvArgs;
|
|
52762
52899
|
this.launchProcess = options.launchProcess ?? launchACPProcess;
|
|
52763
52900
|
this.sessionCwd = import_node_path8.default.resolve(process.cwd());
|
|
52764
52901
|
}
|
|
@@ -52887,7 +53024,8 @@ var ACPBridge = class extends withDisposable(class {
|
|
|
52887
53024
|
status: "ready",
|
|
52888
53025
|
agent: this.profile.agent,
|
|
52889
53026
|
sessionIdStrategy: this.profile.sessionIdStrategy,
|
|
52890
|
-
sessionCwd: this.sessionCwd
|
|
53027
|
+
sessionCwd: this.sessionCwd,
|
|
53028
|
+
tvArgs: [...this.tvArgs]
|
|
52891
53029
|
});
|
|
52892
53030
|
return;
|
|
52893
53031
|
}
|
|
@@ -52912,6 +53050,7 @@ var ACPServer = class extends withDisposable(class {
|
|
|
52912
53050
|
authToken;
|
|
52913
53051
|
authRequired;
|
|
52914
53052
|
profile;
|
|
53053
|
+
getAgentTVArgs;
|
|
52915
53054
|
constructor(options) {
|
|
52916
53055
|
super();
|
|
52917
53056
|
if (!options.profile) {
|
|
@@ -52920,6 +53059,7 @@ var ACPServer = class extends withDisposable(class {
|
|
|
52920
53059
|
this.authToken = options.authToken;
|
|
52921
53060
|
this.authRequired = options.authRequired;
|
|
52922
53061
|
this.profile = options.profile;
|
|
53062
|
+
this.getAgentTVArgs = options.getAgentTVArgs;
|
|
52923
53063
|
this.wsServer = new import_websocket_server.default({ noServer: true });
|
|
52924
53064
|
this.wsServer.on("connection", (socket, request) => {
|
|
52925
53065
|
if (this.authRequired && !isAuthorizedQueryToken(request.url, this.authToken)) {
|
|
@@ -52928,6 +53068,7 @@ var ACPServer = class extends withDisposable(class {
|
|
|
52928
53068
|
}
|
|
52929
53069
|
const bridge = new ACPBridge({
|
|
52930
53070
|
profile: this.profile,
|
|
53071
|
+
tvArgs: this.getAgentTVArgs(),
|
|
52931
53072
|
send: (message) => {
|
|
52932
53073
|
if (socket.readyState === import_websocket.default.OPEN) {
|
|
52933
53074
|
socket.send(JSON.stringify(message));
|
|
@@ -52977,14 +53118,14 @@ var ACPServer = class extends withDisposable(class {
|
|
|
52977
53118
|
};
|
|
52978
53119
|
|
|
52979
53120
|
// ../server/src/bind-addresses.ts
|
|
52980
|
-
var
|
|
53121
|
+
var import_node_net3 = require("node:net");
|
|
52981
53122
|
var LOCALHOST_IPV4 = "127.0.0.1";
|
|
52982
53123
|
var ANY_IPV4 = "0.0.0.0";
|
|
52983
53124
|
function resolveBindAddresses(listenValues = []) {
|
|
52984
53125
|
const addresses = [LOCALHOST_IPV4];
|
|
52985
53126
|
for (const address of splitListenValues(listenValues)) {
|
|
52986
|
-
if (!(0,
|
|
52987
|
-
throw new Error(`Invalid
|
|
53127
|
+
if (!(0, import_node_net3.isIPv4)(address)) {
|
|
53128
|
+
throw new Error(`Invalid listen address \`${address}\`. Use an IPv4 address.`);
|
|
52988
53129
|
}
|
|
52989
53130
|
addresses.push(address);
|
|
52990
53131
|
}
|
|
@@ -53216,7 +53357,7 @@ var StartupBindError = class extends Error {
|
|
|
53216
53357
|
this.name = "StartupBindError";
|
|
53217
53358
|
}
|
|
53218
53359
|
};
|
|
53219
|
-
function buildStartupBindFailureRecord(attempts) {
|
|
53360
|
+
function buildStartupBindFailureRecord(attempts, launchMode) {
|
|
53220
53361
|
return {
|
|
53221
53362
|
reason: "required-listener-bind-failed",
|
|
53222
53363
|
exitStatus: STARTUP_BIND_EXIT_STATUS,
|
|
@@ -53231,7 +53372,7 @@ function buildStartupBindFailureRecord(attempts) {
|
|
|
53231
53372
|
...code === void 0 ? {} : { code },
|
|
53232
53373
|
...typeof serializedError.syscall === "string" ? { syscall: serializedError.syscall } : {},
|
|
53233
53374
|
cause: bindFailureCause(attempt, code),
|
|
53234
|
-
hint: bindFailureHint(attempt, code)
|
|
53375
|
+
hint: bindFailureHint(attempt, code, launchMode)
|
|
53235
53376
|
};
|
|
53236
53377
|
})
|
|
53237
53378
|
};
|
|
@@ -53248,9 +53389,9 @@ function bindFailureCause(attempt, code) {
|
|
|
53248
53389
|
}
|
|
53249
53390
|
return `Cannot bind ${attempt.address}:${attempt.port}: ${attempt.error.message}`;
|
|
53250
53391
|
}
|
|
53251
|
-
function bindFailureHint(attempt, code) {
|
|
53252
|
-
if (code === "EADDRNOTAVAIL" && isCgnatAddress(attempt.address)) {
|
|
53253
|
-
return `Address ${attempt.address} is not assigned to any interface. If this is a Tailscale IP, tailscaled may not be up yet (transient at boot), or the tailnet IP may have changed (permanent until
|
|
53392
|
+
function bindFailureHint(attempt, code, launchMode) {
|
|
53393
|
+
if (code === "EADDRNOTAVAIL" && launchMode === "daemon" && isCgnatAddress(attempt.address)) {
|
|
53394
|
+
return `Address ${attempt.address} is not assigned to any interface. If this is a Tailscale IP, tailscaled may not be up yet (transient at boot), or the tailnet IP may have changed (permanent until the config file's \`listen\` setting is corrected). The service manager will retry.`;
|
|
53254
53395
|
}
|
|
53255
53396
|
if (code === "EADDRNOTAVAIL") {
|
|
53256
53397
|
return `Confirm that ${attempt.address} is assigned to a local interface, then retry.`;
|
|
@@ -53984,7 +54125,7 @@ function disabledTelemetryRuntime() {
|
|
|
53984
54125
|
|
|
53985
54126
|
// ../server/src/updates/version.ts
|
|
53986
54127
|
function readVersionStamp() {
|
|
53987
|
-
if ("1.4.
|
|
54128
|
+
if ("1.4.8".length > 0) return "1.4.8";
|
|
53988
54129
|
return void 0;
|
|
53989
54130
|
}
|
|
53990
54131
|
function resolveUpdateReleaseVersion(env = process.env, stamp = readVersionStamp()) {
|
|
@@ -54150,7 +54291,6 @@ function parseIntervalOverride(value) {
|
|
|
54150
54291
|
// ../server/src/server.ts
|
|
54151
54292
|
var import_meta = {};
|
|
54152
54293
|
var HTTP_UNAUTHORIZED_STATUS = 401;
|
|
54153
|
-
var LOOPBACK_IPV4 = "127.0.0.1";
|
|
54154
54294
|
var CANONICAL_VERSION_DIRECTORY_PATTERN = /^v\d+(?:\.\d+)?$/;
|
|
54155
54295
|
function resolveCanonicalVersionDirectories(canonicalDir) {
|
|
54156
54296
|
const canonicalRoot = import_node_path13.default.resolve(canonicalDir);
|
|
@@ -54185,6 +54325,7 @@ var Server = class {
|
|
|
54185
54325
|
authMode;
|
|
54186
54326
|
bindAddresses;
|
|
54187
54327
|
telemetryOptions;
|
|
54328
|
+
launchMode;
|
|
54188
54329
|
telemetryRuntime = null;
|
|
54189
54330
|
updateChannel = null;
|
|
54190
54331
|
listeningPort;
|
|
@@ -54197,6 +54338,7 @@ var Server = class {
|
|
|
54197
54338
|
this.authMode = options.auth === true ? "auth" : options.auth === false ? "no-auth" : "none";
|
|
54198
54339
|
this.bindAddresses = resolveBindAddresses(options.host ? [options.host] : options.listen);
|
|
54199
54340
|
this.telemetryOptions = options.telemetry ?? {};
|
|
54341
|
+
this.launchMode = this.telemetryOptions.launchMode ?? "cli";
|
|
54200
54342
|
this.listeningPort = this.port;
|
|
54201
54343
|
this.baseURLs = [];
|
|
54202
54344
|
this.app = (0, import_express2.default)();
|
|
@@ -54233,7 +54375,12 @@ var Server = class {
|
|
|
54233
54375
|
this.acp = options.acpProfile ? new ACPServer({
|
|
54234
54376
|
authToken: this.store.authToken,
|
|
54235
54377
|
authRequired: this.authRequired,
|
|
54236
|
-
profile: options.acpProfile
|
|
54378
|
+
profile: options.acpProfile,
|
|
54379
|
+
getAgentTVArgs: () => buildACPAgentTVArgs({
|
|
54380
|
+
home: this.store.storagePath,
|
|
54381
|
+
configuredPort: this.port,
|
|
54382
|
+
acquiredPort: this.getListeningPort()
|
|
54383
|
+
})
|
|
54237
54384
|
}) : null;
|
|
54238
54385
|
registerRoutes(this.app, this.store, {
|
|
54239
54386
|
requireAuth: this.requireAuthorization,
|
|
@@ -54333,7 +54480,11 @@ var Server = class {
|
|
|
54333
54480
|
}
|
|
54334
54481
|
}
|
|
54335
54482
|
if (attempts.some((attempt) => attempt.outcome === "failed")) {
|
|
54336
|
-
log(
|
|
54483
|
+
log(
|
|
54484
|
+
this.store.storagePath,
|
|
54485
|
+
"server startup failed",
|
|
54486
|
+
buildStartupBindFailureRecord(attempts, this.launchMode)
|
|
54487
|
+
);
|
|
54337
54488
|
await Promise.allSettled(boundServers.map((server) => this.closeServer(server)));
|
|
54338
54489
|
throw new StartupBindError(attempts);
|
|
54339
54490
|
}
|
|
@@ -54348,16 +54499,6 @@ var Server = class {
|
|
|
54348
54499
|
log(this.store.storagePath, "server started", { snapshot: this.configurationSnapshot() });
|
|
54349
54500
|
if (!this.authRequired) {
|
|
54350
54501
|
log(this.store.storagePath, "server running without bearer-token enforcement", { authMode: this.authMode });
|
|
54351
|
-
const nonLoopbackBoundAddresses = this.getNonLoopbackBoundAddresses();
|
|
54352
|
-
process.stderr.write(
|
|
54353
|
-
"WARNING: running without an auth token. Tokenless mode is insecure for typical setups \u2014 be sure you mean to run without authentication. Start with --auth to require the bearer token.\n"
|
|
54354
|
-
);
|
|
54355
|
-
if (this.authMode === "no-auth" && nonLoopbackBoundAddresses.length > 0) {
|
|
54356
|
-
process.stderr.write(
|
|
54357
|
-
`Non-loopback listeners without auth: ${nonLoopbackBoundAddresses.join(", ")}
|
|
54358
|
-
`
|
|
54359
|
-
);
|
|
54360
|
-
}
|
|
54361
54502
|
}
|
|
54362
54503
|
return this.httpServer;
|
|
54363
54504
|
}
|
|
@@ -54437,9 +54578,6 @@ var Server = class {
|
|
|
54437
54578
|
server.listen(port, address);
|
|
54438
54579
|
});
|
|
54439
54580
|
}
|
|
54440
|
-
getNonLoopbackBoundAddresses() {
|
|
54441
|
-
return this.baseURLs.map((url2) => new URL(url2).hostname).filter((address) => address !== LOOPBACK_IPV4);
|
|
54442
|
-
}
|
|
54443
54581
|
/**
|
|
54444
54582
|
* Update-channel poller lifecycle (specs/arch/updates/update-channel.md):
|
|
54445
54583
|
* owned by the Server — started here, stopped in dispose() with its timer
|
|
@@ -54454,7 +54592,6 @@ var Server = class {
|
|
|
54454
54592
|
}
|
|
54455
54593
|
async startTelemetry() {
|
|
54456
54594
|
const version2 = this.telemetryOptions.version ?? readServerPackageVersion();
|
|
54457
|
-
const launchMode = this.telemetryOptions.launchMode ?? "cli";
|
|
54458
54595
|
const runtime = await createTelemetryRuntime({
|
|
54459
54596
|
storagePath: this.store.storagePath,
|
|
54460
54597
|
version: version2,
|
|
@@ -54481,7 +54618,7 @@ var Server = class {
|
|
|
54481
54618
|
port: this.getListeningPort(),
|
|
54482
54619
|
bindAddresses: this.bindAddresses,
|
|
54483
54620
|
authMode: this.authMode,
|
|
54484
|
-
launchMode,
|
|
54621
|
+
launchMode: this.launchMode,
|
|
54485
54622
|
installedByAgent: this.telemetryOptions.installedByAgent ?? null
|
|
54486
54623
|
}),
|
|
54487
54624
|
{ telemetry_opted_out: runtime.boot.state.optedOut }
|
|
@@ -54530,8 +54667,8 @@ var Server = class {
|
|
|
54530
54667
|
};
|
|
54531
54668
|
};
|
|
54532
54669
|
function readServerPackageVersion() {
|
|
54533
|
-
if ("1.4.
|
|
54534
|
-
return telemetryVersion("1.4.
|
|
54670
|
+
if ("1.4.8".length > 0) {
|
|
54671
|
+
return telemetryVersion("1.4.8");
|
|
54535
54672
|
}
|
|
54536
54673
|
const packageJsonPath = import_node_path13.default.resolve(import_node_path13.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
|
|
54537
54674
|
if (!(0, import_node_fs11.existsSync)(packageJsonPath)) return telemetryVersion("0.0.0");
|
|
@@ -56674,12 +56811,35 @@ var import_meta2 = {};
|
|
|
56674
56811
|
var DAEMON_NAME = "com.television.server";
|
|
56675
56812
|
var TELEMETRY_LAUNCH_MODE_ENV = "TELEVISION_LAUNCH_MODE";
|
|
56676
56813
|
var SKILL_INSTALL_TELEMETRY_TIMEOUT_MS2 = 1e3;
|
|
56814
|
+
var MIN_CLIENT_PORT = 1;
|
|
56677
56815
|
var MAX_PORT2 = 65535;
|
|
56678
56816
|
var MAX_PROCESS_EXIT_STATUS = 255;
|
|
56817
|
+
var LOOPBACK_IPV4 = "127.0.0.1";
|
|
56818
|
+
var CONFIG_KEYS2 = ["port", "listen", "auth", "installedByAgent"];
|
|
56679
56819
|
var HTTP_UNAUTHORIZED_STATUS2 = 401;
|
|
56680
56820
|
var HELP_POINTER = "Television ships bundled skills. The main skill is `television` \u2014 keep its guidance available for channels, lifecycle, the `tv` CLI, artifact workflow, and theming. Re-read it only if it is not already in context or you know the installed skill changed. Additional `tv-*` skills cover specialized artifact types. Install all bundled skills with `tv skills install <path>` (e.g. ~/.openclaw/skills) or `tv skills install -i`.";
|
|
56681
|
-
var PORT_ZERO_WARNING = "WARNING:
|
|
56821
|
+
var PORT_ZERO_WARNING = "WARNING: config port 0 lets the operating system choose this server's port. Commands that contact this server must pass --port <port>, using the port from the startup URL.";
|
|
56822
|
+
var TOKENLESS_WARNING = "WARNING: running without an auth token. Tokenless mode is insecure for typical setups \u2014 be sure you mean to run without authentication. Run `tv config set auth true` and restart to require the bearer token.";
|
|
56682
56823
|
var SAFE_ARG_PATTERN = /^[A-Za-z0-9_./:@%+=,-]+$/;
|
|
56824
|
+
var RETIRED_SERVE_OPTIONS = ["--port", "--listen", "--auth", "--no-auth", "--installed-by-agent"];
|
|
56825
|
+
var RETIRED_VALUE_OPTIONS = ["--port", "--listen", "--installed-by-agent", "--storage-path"];
|
|
56826
|
+
var ADMIN_GUIDE_URL = "https://television.run/install.md";
|
|
56827
|
+
var RETIRED_OPTIONS_GUIDANCE = [
|
|
56828
|
+
"Server settings and the storage directory are no longer command options:",
|
|
56829
|
+
"",
|
|
56830
|
+
" --port <number> -> tv config set port <number>",
|
|
56831
|
+
" --listen <ipv4>[,<ipv4>...] -> tv config set listen <ipv4>[,<ipv4>...]",
|
|
56832
|
+
" --auth -> tv config set auth true (the default)",
|
|
56833
|
+
" --no-auth -> tv config set auth false",
|
|
56834
|
+
" --installed-by-agent <name> -> tv config set installedByAgent <name>",
|
|
56835
|
+
" --storage-path <path> -> tv --home <path> <command>, or write <path> into ~/.tv-home",
|
|
56836
|
+
"",
|
|
56837
|
+
"tv config set writes the config file in the Television home. For a home other than the default, put --home <path> before the command name, as in tv --home <path> config set port <number>.",
|
|
56838
|
+
"After changing settings, rerun tv serve --persist to update an installed service.",
|
|
56839
|
+
`The administrator guide covers the full upgrade and is written for your agent to carry out: ${ADMIN_GUIDE_URL}`
|
|
56840
|
+
].join("\n");
|
|
56841
|
+
var RETIRED_SERVICE_RECORD = "service runs from a retired service definition";
|
|
56842
|
+
var LEGACY_VARIABLES_WARNING = "WARNING: TELEVISION_PORT or TELEVISION_STORAGE_PATH is set in this environment, and Television no longer uses either. Set the server's port with tv config set port <number>. Television uses the home given with tv --home <path> <command>, otherwise the path written in ~/.tv-home, otherwise ~/.television.";
|
|
56683
56843
|
var CLIDirectiveError = class extends Error {
|
|
56684
56844
|
name = "CLIDirectiveError";
|
|
56685
56845
|
};
|
|
@@ -56737,8 +56897,8 @@ function resolveVercelSkillsInstallerBin() {
|
|
|
56737
56897
|
return localRequire.resolve("skills/bin/cli.mjs");
|
|
56738
56898
|
}
|
|
56739
56899
|
function readCLIVersion() {
|
|
56740
|
-
if ("1.4.
|
|
56741
|
-
return "1.4.
|
|
56900
|
+
if ("1.4.8".length > 0) {
|
|
56901
|
+
return "1.4.8";
|
|
56742
56902
|
}
|
|
56743
56903
|
const devPackageJsonPath = import_node_path20.default.join(getDevPackageDir(), "package.json");
|
|
56744
56904
|
if (!(0, import_node_fs18.existsSync)(devPackageJsonPath)) {
|
|
@@ -56799,28 +56959,72 @@ function formatEnteredInvocation(argv) {
|
|
|
56799
56959
|
function formatEnteredCommand(argv) {
|
|
56800
56960
|
return argv.length > 0 ? argv.map(formatArgvToken).join(" ") : "tv";
|
|
56801
56961
|
}
|
|
56802
|
-
function
|
|
56962
|
+
function exitStatusFromError(error48) {
|
|
56963
|
+
if (typeof error48 !== "object" || error48 === null || !("exitStatus" in error48)) return void 0;
|
|
56964
|
+
const exitStatus = error48.exitStatus;
|
|
56965
|
+
return typeof exitStatus === "number" && Number.isInteger(exitStatus) && exitStatus > 0 && exitStatus <= MAX_PROCESS_EXIT_STATUS ? exitStatus : void 0;
|
|
56966
|
+
}
|
|
56967
|
+
function splitOptionToken(token) {
|
|
56968
|
+
const equals = token.indexOf("=");
|
|
56969
|
+
return token.startsWith("--") && equals !== -1 ? [token.slice(0, equals), token.slice(equals + 1)] : [token, void 0];
|
|
56970
|
+
}
|
|
56971
|
+
function findCommandName(argv) {
|
|
56803
56972
|
for (let index = 0; index < argv.length; index += 1) {
|
|
56804
|
-
const
|
|
56805
|
-
if (
|
|
56806
|
-
|
|
56973
|
+
const token = argv[index];
|
|
56974
|
+
if (token === "--") return void 0;
|
|
56975
|
+
if (!token.startsWith("-")) return token;
|
|
56976
|
+
if (token === "--home" || RETIRED_VALUE_OPTIONS.includes(token)) index += 1;
|
|
56977
|
+
}
|
|
56978
|
+
return void 0;
|
|
56979
|
+
}
|
|
56980
|
+
function extractRetiredOptions(argv) {
|
|
56981
|
+
const command = findCommandName(argv);
|
|
56982
|
+
const retired = [];
|
|
56983
|
+
const remaining = [];
|
|
56984
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
56985
|
+
const token = argv[index];
|
|
56986
|
+
if (token === "--") {
|
|
56987
|
+
remaining.push(...argv.slice(index));
|
|
56988
|
+
break;
|
|
56807
56989
|
}
|
|
56808
|
-
|
|
56809
|
-
|
|
56990
|
+
const [name, inlineValue] = splitOptionToken(token);
|
|
56991
|
+
const isRetired = name === "--storage-path" || command === "serve" && RETIRED_SERVE_OPTIONS.includes(name);
|
|
56992
|
+
if (!isRetired) {
|
|
56993
|
+
remaining.push(token);
|
|
56994
|
+
} else if (inlineValue !== void 0 || !RETIRED_VALUE_OPTIONS.includes(name)) {
|
|
56995
|
+
retired.push({ name, value: inlineValue });
|
|
56996
|
+
} else {
|
|
56997
|
+
retired.push({ name, value: argv[index + 1] });
|
|
56998
|
+
index += 1;
|
|
56810
56999
|
}
|
|
56811
57000
|
}
|
|
56812
|
-
return
|
|
57001
|
+
return { command, retired, remaining };
|
|
56813
57002
|
}
|
|
56814
|
-
function
|
|
56815
|
-
|
|
56816
|
-
const
|
|
56817
|
-
|
|
57003
|
+
function readRetiredServiceDefinition(command, retired, remaining, processEnv) {
|
|
57004
|
+
const options = remaining.slice(0, remaining.includes("--") ? remaining.indexOf("--") : void 0);
|
|
57005
|
+
const otherForm = options.some((token) => ["--persist", "--persist-uninstall", "--home"].includes(splitOptionToken(token)[0]));
|
|
57006
|
+
if (resolveTelemetryLaunchMode(processEnv) !== "daemon" || command !== "serve" || otherForm) return void 0;
|
|
57007
|
+
const values = (name) => retired.filter((option) => option.name === name).map((option) => option.value ?? "");
|
|
57008
|
+
const settings = { auth: values("--auth").length > 0 };
|
|
57009
|
+
const port = values("--port").at(-1);
|
|
57010
|
+
if (port !== void 0) {
|
|
57011
|
+
settings.port = parseWholePort(port, 0) ?? port;
|
|
57012
|
+
} else {
|
|
57013
|
+
const environmentPort = parseWholePort(processEnv.TELEVISION_PORT ?? "", 0);
|
|
57014
|
+
if (environmentPort !== void 0) settings.port = environmentPort;
|
|
57015
|
+
}
|
|
57016
|
+
const listen = values("--listen");
|
|
57017
|
+
if (listen.length > 0) settings.listen = listen.flatMap((value) => value.split(",").map((address) => address.trim()).filter(Boolean));
|
|
57018
|
+
const installedByAgent = values("--installed-by-agent").at(-1);
|
|
57019
|
+
if (installedByAgent !== void 0) settings.installedByAgent = installedByAgent;
|
|
57020
|
+
const storagePath = values("--storage-path").at(-1);
|
|
57021
|
+
return { ...storagePath === void 0 ? {} : { storagePath }, settings };
|
|
56818
57022
|
}
|
|
56819
|
-
function formatCLIError(error48,
|
|
57023
|
+
function formatCLIError(error48, invocation) {
|
|
56820
57024
|
if (error48 instanceof CLIDirectiveError) {
|
|
56821
57025
|
return error48.message;
|
|
56822
57026
|
}
|
|
56823
|
-
if (error48 instanceof ValidationError) {
|
|
57027
|
+
if (error48 instanceof ValidationError || error48 instanceof TelevisionConfigError) {
|
|
56824
57028
|
return ensureHelpPointer(error48.message);
|
|
56825
57029
|
}
|
|
56826
57030
|
if (typeof error48 === "object" && error48 !== null && "serverURL" in error48 && "message" in error48) {
|
|
@@ -56829,7 +57033,7 @@ function formatCLIError(error48, argv = []) {
|
|
|
56829
57033
|
const status = typeof error48.status === "number" ? Number(error48.status) : void 0;
|
|
56830
57034
|
if (status === HTTP_UNAUTHORIZED_STATUS2) {
|
|
56831
57035
|
return ensureHelpPointer(
|
|
56832
|
-
`Television server at ${serverURL} rejected the request as unauthorized. Check the token in ${import_node_path20.default.join(
|
|
57036
|
+
`Television server at ${serverURL} rejected the request as unauthorized. Check the token in ${import_node_path20.default.join(invocation.home ?? "<home>", "state", "token")}.`
|
|
56833
57037
|
);
|
|
56834
57038
|
}
|
|
56835
57039
|
if (status !== void 0) {
|
|
@@ -56839,44 +57043,56 @@ function formatCLIError(error48, argv = []) {
|
|
|
56839
57043
|
}
|
|
56840
57044
|
return error48 instanceof Error ? error48.message : String(error48);
|
|
56841
57045
|
}
|
|
56842
|
-
function
|
|
56843
|
-
if (!/^\d+$/.test(value))
|
|
56844
|
-
throw new InvalidArgumentError(`port must be a decimal integer between 0 and ${MAX_PORT2}`);
|
|
56845
|
-
}
|
|
57046
|
+
function parseWholePort(value, minimum) {
|
|
57047
|
+
if (!/^\d+$/.test(value)) return void 0;
|
|
56846
57048
|
const port = Number(value);
|
|
56847
|
-
|
|
56848
|
-
throw new InvalidArgumentError(`port must be a decimal integer between 0 and ${MAX_PORT2}`);
|
|
56849
|
-
}
|
|
56850
|
-
return port;
|
|
56851
|
-
}
|
|
56852
|
-
function collectOption(value, previous = []) {
|
|
56853
|
-
return [...previous, value];
|
|
57049
|
+
return Number.isSafeInteger(port) && port >= minimum && port <= MAX_PORT2 ? port : void 0;
|
|
56854
57050
|
}
|
|
56855
|
-
function
|
|
56856
|
-
|
|
56857
|
-
|
|
57051
|
+
function parseClientPortOption(value) {
|
|
57052
|
+
const port = parseWholePort(value, MIN_CLIENT_PORT);
|
|
57053
|
+
if (port === void 0) {
|
|
57054
|
+
throw new InvalidArgumentError(`port must be a whole decimal integer from ${MIN_CLIENT_PORT} through ${MAX_PORT2}`);
|
|
56858
57055
|
}
|
|
56859
|
-
|
|
56860
|
-
return getTelevisionPortEnv() ?? DEFAULT_SERVER_PORT;
|
|
56861
|
-
} catch (error48) {
|
|
56862
|
-
throw createDirectiveError(error48 instanceof Error ? error48.message : String(error48));
|
|
56863
|
-
}
|
|
56864
|
-
}
|
|
56865
|
-
function buildLocalServerURL(port) {
|
|
56866
|
-
return buildServerURL(DEFAULT_SERVER_HOST, resolveClientPort(port));
|
|
57056
|
+
return port;
|
|
56867
57057
|
}
|
|
56868
|
-
function
|
|
56869
|
-
const
|
|
56870
|
-
|
|
56871
|
-
|
|
56872
|
-
|
|
56873
|
-
|
|
56874
|
-
|
|
56875
|
-
|
|
57058
|
+
function parseConfigSetPairs(pairs, invocation) {
|
|
57059
|
+
const keyList = CONFIG_KEYS2.join(", ");
|
|
57060
|
+
if (pairs.length === 0) {
|
|
57061
|
+
throw createDirectiveError(`${invocation} requires at least one <key> <value> pair. Keys: ${keyList}.`);
|
|
57062
|
+
}
|
|
57063
|
+
if (pairs.length % 2 !== 0) {
|
|
57064
|
+
throw createDirectiveError(`${invocation} requires a value for ${formatArgvToken(pairs[pairs.length - 1])}.`);
|
|
57065
|
+
}
|
|
57066
|
+
const keys = [];
|
|
57067
|
+
const changes = {};
|
|
57068
|
+
for (let index = 0; index < pairs.length; index += 2) {
|
|
57069
|
+
const key = pairs[index];
|
|
57070
|
+
const value = pairs[index + 1];
|
|
57071
|
+
if (!CONFIG_KEYS2.includes(key)) {
|
|
57072
|
+
throw createDirectiveError(`${invocation} does not support config key ${formatArgvToken(key)}. Keys: ${keyList}.`);
|
|
57073
|
+
}
|
|
57074
|
+
if (keys.includes(key)) {
|
|
57075
|
+
throw createDirectiveError(`${invocation} sets ${key} more than once.`);
|
|
57076
|
+
}
|
|
57077
|
+
keys.push(key);
|
|
57078
|
+
if (key === "port") {
|
|
57079
|
+
const port = parseWholePort(value, 0);
|
|
57080
|
+
if (port === void 0) {
|
|
57081
|
+
throw createDirectiveError(`${invocation} received an invalid port value ${formatArgvToken(value)}: use a whole decimal integer from 0 through ${MAX_PORT2}.`);
|
|
57082
|
+
}
|
|
57083
|
+
changes.port = port;
|
|
57084
|
+
} else if (key === "listen") {
|
|
57085
|
+
changes.listen = value.split(",").map((address) => address.trim()).filter((address) => address.length > 0);
|
|
57086
|
+
} else if (key === "auth") {
|
|
57087
|
+
if (value !== "true" && value !== "false") {
|
|
57088
|
+
throw createDirectiveError(`${invocation} received an invalid auth value ${formatArgvToken(value)}: use true or false.`);
|
|
57089
|
+
}
|
|
57090
|
+
changes.auth = value === "true";
|
|
57091
|
+
} else {
|
|
57092
|
+
changes.installedByAgent = value;
|
|
57093
|
+
}
|
|
56876
57094
|
}
|
|
56877
|
-
|
|
56878
|
-
if (wantsNoAuth) return false;
|
|
56879
|
-
return void 0;
|
|
57095
|
+
return { keys, changes };
|
|
56880
57096
|
}
|
|
56881
57097
|
function resolveStaticDir() {
|
|
56882
57098
|
if (process.argv[1]) {
|
|
@@ -57031,29 +57247,22 @@ function formatCommanderError(argv, error48) {
|
|
|
57031
57247
|
}
|
|
57032
57248
|
return null;
|
|
57033
57249
|
}
|
|
57034
|
-
function readAuthToken(
|
|
57035
|
-
const tokenPath = import_node_path20.default.join(
|
|
57250
|
+
function readAuthToken(home) {
|
|
57251
|
+
const tokenPath = import_node_path20.default.join(home, "state", "token");
|
|
57036
57252
|
if (!(0, import_node_fs18.existsSync)(tokenPath)) {
|
|
57037
57253
|
return void 0;
|
|
57038
57254
|
}
|
|
57039
57255
|
const token = (0, import_node_fs18.readFileSync)(tokenPath, "utf8").trim();
|
|
57040
57256
|
return token.length > 0 ? token : void 0;
|
|
57041
57257
|
}
|
|
57042
|
-
function readOrCreateAuthToken(
|
|
57258
|
+
function readOrCreateAuthToken(home) {
|
|
57043
57259
|
return new ServerStore({
|
|
57044
|
-
storagePath,
|
|
57260
|
+
storagePath: home,
|
|
57045
57261
|
installOnboardingChannels: false
|
|
57046
57262
|
}).authToken;
|
|
57047
57263
|
}
|
|
57048
|
-
function buildDaemonServeArgs(
|
|
57049
|
-
|
|
57050
|
-
for (const address of options.listen ?? []) args.push("--listen", address);
|
|
57051
|
-
if (options.auth === true) args.push("--auth");
|
|
57052
|
-
if (options.auth === false) args.push("--no-auth");
|
|
57053
|
-
if (options.port !== void 0) args.push("--port", String(options.port));
|
|
57054
|
-
if (options.storagePath) args.push("--storage-path", options.storagePath);
|
|
57055
|
-
if (options.installedByAgent) args.push("--installed-by-agent", options.installedByAgent);
|
|
57056
|
-
return args;
|
|
57264
|
+
function buildDaemonServeArgs(home) {
|
|
57265
|
+
return [process.argv[1] ?? "tv", "--home", home, "serve"];
|
|
57057
57266
|
}
|
|
57058
57267
|
function redactSensitiveEnv(env) {
|
|
57059
57268
|
const redacted = {};
|
|
@@ -57079,7 +57288,7 @@ function createEnvironment(environment) {
|
|
|
57079
57288
|
// resolved content root when one resolved
|
|
57080
57289
|
// (specs/arch/cli/index.md, build and packaged asset layout).
|
|
57081
57290
|
store: new ServerStore({
|
|
57082
|
-
storagePath: options.
|
|
57291
|
+
storagePath: options.home,
|
|
57083
57292
|
...options.bundledViewsPath ? { bundledViewsPath: options.bundledViewsPath } : {},
|
|
57084
57293
|
...options.onboardingContentPath ? { onboardingContentPath: options.onboardingContentPath } : {},
|
|
57085
57294
|
...options.bundledThemesPath ? { bundledThemesPath: options.bundledThemesPath } : {},
|
|
@@ -57087,17 +57296,18 @@ function createEnvironment(environment) {
|
|
|
57087
57296
|
}),
|
|
57088
57297
|
listen: options.listen,
|
|
57089
57298
|
port: options.port,
|
|
57299
|
+
// Always explicit: the Server constructor's own default is tokenless.
|
|
57090
57300
|
auth: options.auth,
|
|
57091
57301
|
staticDir: options.staticDir,
|
|
57092
57302
|
canonicalDir: options.canonicalDir,
|
|
57093
57303
|
acpProfile: options.acpProfile,
|
|
57094
57304
|
telemetry: {
|
|
57095
|
-
launchMode: options.launchMode
|
|
57305
|
+
launchMode: options.launchMode,
|
|
57096
57306
|
installedByAgent: options.installedByAgent ?? null
|
|
57097
57307
|
}
|
|
57098
57308
|
})),
|
|
57099
|
-
createDaemon: environment.createDaemon ?? ((options
|
|
57100
|
-
const args = buildDaemonServeArgs(options);
|
|
57309
|
+
createDaemon: environment.createDaemon ?? ((options) => {
|
|
57310
|
+
const args = options ? buildDaemonServeArgs(options.home) : [process.argv[1] ?? "tv", "serve"];
|
|
57101
57311
|
return new Daemon({
|
|
57102
57312
|
name: DAEMON_NAME,
|
|
57103
57313
|
description: "Television server \u2014 virtual display for agents",
|
|
@@ -57106,7 +57316,7 @@ function createEnvironment(environment) {
|
|
|
57106
57316
|
// the service is how a later Node selection takes effect.
|
|
57107
57317
|
command: process.execPath,
|
|
57108
57318
|
args,
|
|
57109
|
-
env: options
|
|
57319
|
+
env: options?.env
|
|
57110
57320
|
});
|
|
57111
57321
|
}),
|
|
57112
57322
|
resolveStaticDir: environment.resolveStaticDir ?? (() => resolveStaticDir()),
|
|
@@ -57127,160 +57337,163 @@ function createEnvironment(environment) {
|
|
|
57127
57337
|
onSignal: environment.onSignal ?? ((signal, handler) => process.on(signal, handler))
|
|
57128
57338
|
};
|
|
57129
57339
|
}
|
|
57130
|
-
function
|
|
57131
|
-
|
|
57340
|
+
function startRetiredService(home, definition) {
|
|
57341
|
+
const wroteConfig = !(0, import_node_fs18.existsSync)(import_node_path20.default.join(home, "config.json"));
|
|
57342
|
+
if (wroteConfig) updateTelevisionConfig(home, definition.settings);
|
|
57343
|
+
log(home, RETIRED_SERVICE_RECORD, {
|
|
57344
|
+
wroteConfig,
|
|
57345
|
+
guidance: `Reinstall the service with \`tv serve --persist\`, adding \`--home ${home}\` before \`serve\` unless this is the default home, so it runs from the home and its config file. The administrator guide describes the upgrade and is written for an agent to carry out: ${ADMIN_GUIDE_URL}`
|
|
57346
|
+
});
|
|
57132
57347
|
}
|
|
57133
|
-
function createProgram(env,
|
|
57348
|
+
function createProgram(env, invocation = { argv: [] }) {
|
|
57349
|
+
const { argv } = invocation;
|
|
57134
57350
|
const program2 = new Command();
|
|
57135
|
-
const
|
|
57136
|
-
if (
|
|
57137
|
-
|
|
57351
|
+
const resolveHome = () => {
|
|
57352
|
+
if (invocation.home !== void 0) return invocation.home;
|
|
57353
|
+
try {
|
|
57354
|
+
invocation.home = resolveTelevisionHome({
|
|
57355
|
+
homeOption: invocation.retiredService?.storagePath ?? program2.opts().home,
|
|
57356
|
+
operatingSystemHomeDir: env.resolveHomeDir(),
|
|
57357
|
+
cwd: process.cwd()
|
|
57358
|
+
});
|
|
57359
|
+
} catch (error48) {
|
|
57360
|
+
throw createDirectiveError(error48 instanceof Error ? error48.message : String(error48));
|
|
57138
57361
|
}
|
|
57362
|
+
return invocation.home;
|
|
57139
57363
|
};
|
|
57140
|
-
const
|
|
57141
|
-
|
|
57142
|
-
|
|
57143
|
-
|
|
57144
|
-
|
|
57145
|
-
|
|
57364
|
+
const resolveClientPort = (config2, portOption) => {
|
|
57365
|
+
const enteredInvocation = formatEnteredInvocation(argv);
|
|
57366
|
+
if (config2.settings.port === 0) {
|
|
57367
|
+
if (portOption === void 0) {
|
|
57368
|
+
throw createDirectiveError(
|
|
57369
|
+
`${enteredInvocation} requires --port because ${config2.configPath} sets port 0. Pass the port from the Television server's startup URL.`
|
|
57370
|
+
);
|
|
57371
|
+
}
|
|
57372
|
+
return portOption;
|
|
57373
|
+
}
|
|
57374
|
+
if (portOption !== void 0) {
|
|
57375
|
+
throw createDirectiveError(
|
|
57376
|
+
`${enteredInvocation} does not accept --port because ${config2.configPath} sets port ${config2.settings.port}. Omit --port to use the configured port.`
|
|
57377
|
+
);
|
|
57378
|
+
}
|
|
57379
|
+
return config2.settings.port;
|
|
57380
|
+
};
|
|
57381
|
+
const resolveServerURL = (portOption) => {
|
|
57382
|
+
const port = resolveClientPort(readTelevisionConfig(resolveHome()), portOption);
|
|
57383
|
+
return buildServerURL(DEFAULT_SERVER_HOST, port);
|
|
57384
|
+
};
|
|
57385
|
+
const createAuthenticatedClient = (options) => {
|
|
57386
|
+
const serverURL = resolveServerURL(options.port);
|
|
57387
|
+
return env.createClient(serverURL, readAuthToken(resolveHome()));
|
|
57146
57388
|
};
|
|
57147
57389
|
const emitSkillInstallTelemetry = async (input) => {
|
|
57148
57390
|
await bestEffortWithinTimeout2(env.emitSkillInstalledTelemetry({
|
|
57149
|
-
storagePath:
|
|
57391
|
+
storagePath: input.home,
|
|
57150
57392
|
version: telemetryVersion(readCLIVersion()),
|
|
57151
57393
|
agentType: input.agentType,
|
|
57152
57394
|
...input.installedByAgent === void 0 ? {} : { installedByAgent: input.installedByAgent },
|
|
57153
57395
|
env: process.env
|
|
57154
57396
|
}), SKILL_INSTALL_TELEMETRY_TIMEOUT_MS2);
|
|
57155
57397
|
};
|
|
57156
|
-
const uninstallPersistedService = async (
|
|
57398
|
+
const uninstallPersistedService = async (home) => {
|
|
57157
57399
|
const daemon = env.createDaemon();
|
|
57158
57400
|
let outcome = "uninstalled";
|
|
57159
57401
|
try {
|
|
57160
57402
|
await daemon.uninstall();
|
|
57161
57403
|
} catch (error48) {
|
|
57162
57404
|
outcome = "error";
|
|
57163
|
-
log(
|
|
57405
|
+
log(home, "persisted service uninstall failed", { daemonName: DAEMON_NAME, outcome, error: error48 });
|
|
57164
57406
|
throw error48;
|
|
57165
57407
|
}
|
|
57166
|
-
log(
|
|
57408
|
+
log(home, "persisted service uninstalled", { daemonName: DAEMON_NAME, outcome });
|
|
57167
57409
|
writeJSON(env.stdout, { status: "stopped" });
|
|
57168
57410
|
};
|
|
57169
|
-
|
|
57170
|
-
|
|
57171
|
-
|
|
57172
|
-
|
|
57411
|
+
const installPersistedService = async (home, config2) => {
|
|
57412
|
+
const { settings, configPath } = config2;
|
|
57413
|
+
if (settings.port === 0) {
|
|
57414
|
+
throw createDirectiveError(
|
|
57415
|
+
`${formatEnteredInvocation(argv)} requires a stable port, but ${configPath} sets port 0. Choose one with \`tv config set port <number>\`.`
|
|
57416
|
+
);
|
|
57173
57417
|
}
|
|
57174
|
-
|
|
57175
|
-
|
|
57176
|
-
|
|
57177
|
-
|
|
57418
|
+
const profile = resolveACPAgentProfile(process.env);
|
|
57419
|
+
const daemonEnv = buildPersistedACPEnvironment(process.env, { developerHome: env.resolveHomeDir() });
|
|
57420
|
+
daemonEnv[TELEMETRY_LAUNCH_MODE_ENV] = "daemon";
|
|
57421
|
+
if (profile && !isACPCommandResolvable(profile.command, daemonEnv)) {
|
|
57422
|
+
const error48 = createDirectiveError(
|
|
57423
|
+
`Could not find ACP agent command \`${profile.command}\` for \`TELEVISION_ACP_AGENT=${profile.agent}\` on the persisted PATH. Set TELEVISION_ACP_AGENT to \`openclaw\` or \`hermes\`, and ensure the selected agent binary is available on PATH before running \`tv serve --persist\`.`
|
|
57424
|
+
);
|
|
57425
|
+
log(home, "server startup refused before binding", { argv, error: error48 });
|
|
57426
|
+
throw error48;
|
|
57178
57427
|
}
|
|
57179
|
-
const
|
|
57180
|
-
|
|
57181
|
-
|
|
57182
|
-
|
|
57428
|
+
const token = settings.auth ? readOrCreateAuthToken(home) : null;
|
|
57429
|
+
const daemonOptions = { home, env: daemonEnv };
|
|
57430
|
+
const execStart = [process.execPath, ...buildDaemonServeArgs(home)];
|
|
57431
|
+
const daemon = env.createDaemon(daemonOptions);
|
|
57432
|
+
const daemonStatus = await daemon.status();
|
|
57433
|
+
if (daemonStatus.installed || daemonStatus.running) {
|
|
57434
|
+
await daemon.uninstall();
|
|
57183
57435
|
}
|
|
57184
|
-
let auth;
|
|
57185
57436
|
try {
|
|
57186
|
-
|
|
57187
|
-
|
|
57437
|
+
await daemon.install();
|
|
57438
|
+
log(home, "persisted service installed", {
|
|
57439
|
+
daemonName: DAEMON_NAME,
|
|
57440
|
+
execStart,
|
|
57441
|
+
env: redactSensitiveEnv(daemonEnv),
|
|
57442
|
+
replacedExisting: daemonStatus.installed || daemonStatus.running,
|
|
57443
|
+
outcome: "installed"
|
|
57444
|
+
});
|
|
57188
57445
|
} catch (error48) {
|
|
57189
|
-
log(
|
|
57190
|
-
|
|
57191
|
-
|
|
57192
|
-
|
|
57193
|
-
|
|
57194
|
-
|
|
57195
|
-
"tv serve --persist --port 0 requires a stable port; port 0 lets the operating system choose a runtime port and cannot produce a stable persisted-service startup URL. Set TELEVISION_PORT to a stable port or pass a non-zero --port."
|
|
57196
|
-
);
|
|
57197
|
-
}
|
|
57198
|
-
const clientPort = resolveClientPort(opts.port);
|
|
57199
|
-
const storagePath = import_node_path20.default.resolve(opts.storagePath ?? getTelevisionStoragePath());
|
|
57200
|
-
const profile2 = resolveACPAgentProfile(process.env);
|
|
57201
|
-
const daemonEnv = buildPersistedACPEnvironment(process.env, { developerHome: env.resolveHomeDir() });
|
|
57202
|
-
if (process.env.TELEVISION_PORT !== void 0 && process.env.TELEVISION_PORT !== "") {
|
|
57203
|
-
daemonEnv.TELEVISION_PORT = process.env.TELEVISION_PORT;
|
|
57204
|
-
}
|
|
57205
|
-
if (process.env.TELEVISION_STORAGE_PATH !== void 0 && process.env.TELEVISION_STORAGE_PATH !== "") {
|
|
57206
|
-
daemonEnv.TELEVISION_STORAGE_PATH = process.env.TELEVISION_STORAGE_PATH;
|
|
57207
|
-
}
|
|
57208
|
-
daemonEnv[TELEMETRY_LAUNCH_MODE_ENV] = "daemon";
|
|
57209
|
-
if (profile2 && !isACPCommandResolvable(profile2.command, daemonEnv)) {
|
|
57210
|
-
const error48 = createDirectiveError(
|
|
57211
|
-
`Could not find ACP agent command \`${profile2.command}\` for \`TELEVISION_ACP_AGENT=${profile2.agent}\` on the persisted PATH. Set TELEVISION_ACP_AGENT to \`openclaw\` or \`hermes\`, and ensure the selected agent binary is available on PATH before running \`tv serve --persist\`.`
|
|
57212
|
-
);
|
|
57213
|
-
log(storagePath, "server startup refused before binding", { argv, error: error48 });
|
|
57214
|
-
throw error48;
|
|
57215
|
-
}
|
|
57216
|
-
const daemonOptions = {
|
|
57217
|
-
listen: opts.listen,
|
|
57218
|
-
auth,
|
|
57219
|
-
port: opts.port,
|
|
57220
|
-
storagePath,
|
|
57221
|
-
...opts.installedByAgent === void 0 ? {} : { installedByAgent: opts.installedByAgent },
|
|
57222
|
-
env: daemonEnv
|
|
57223
|
-
};
|
|
57224
|
-
const daemon = env.createDaemon(daemonOptions);
|
|
57225
|
-
const daemonStatus = await daemon.status();
|
|
57226
|
-
if (daemonStatus.installed || daemonStatus.running) {
|
|
57227
|
-
await daemon.uninstall();
|
|
57228
|
-
}
|
|
57229
|
-
try {
|
|
57230
|
-
await daemon.install();
|
|
57231
|
-
log(storagePath, "persisted service installed", {
|
|
57232
|
-
daemonName: DAEMON_NAME,
|
|
57233
|
-
execStart: [process.execPath, ...buildDaemonServeArgs(daemonOptions)],
|
|
57234
|
-
env: redactSensitiveEnv(daemonEnv),
|
|
57235
|
-
replacedExisting: daemonStatus.installed || daemonStatus.running,
|
|
57236
|
-
outcome: "installed"
|
|
57237
|
-
});
|
|
57238
|
-
} catch (error48) {
|
|
57239
|
-
log(storagePath, "persisted service install failed", {
|
|
57240
|
-
daemonName: DAEMON_NAME,
|
|
57241
|
-
execStart: [process.execPath, ...buildDaemonServeArgs(daemonOptions)],
|
|
57242
|
-
env: redactSensitiveEnv(daemonEnv),
|
|
57243
|
-
outcome: "error",
|
|
57244
|
-
error: error48
|
|
57245
|
-
});
|
|
57246
|
-
throw error48;
|
|
57247
|
-
}
|
|
57248
|
-
const serverURLs2 = resolveBindAddresses(opts.listen).map((address) => buildServerURL(address, clientPort));
|
|
57249
|
-
writeConnectURLs(env.stdout, serverURLs2, {
|
|
57250
|
-
installed: true,
|
|
57251
|
-
token: auth === true ? readOrCreateAuthToken(storagePath) : null
|
|
57446
|
+
log(home, "persisted service install failed", {
|
|
57447
|
+
daemonName: DAEMON_NAME,
|
|
57448
|
+
execStart,
|
|
57449
|
+
env: redactSensitiveEnv(daemonEnv),
|
|
57450
|
+
outcome: "error",
|
|
57451
|
+
error: error48
|
|
57252
57452
|
});
|
|
57253
|
-
|
|
57453
|
+
throw error48;
|
|
57254
57454
|
}
|
|
57255
|
-
|
|
57455
|
+
const serverURLs = resolveBindAddresses(settings.listen).map((address) => buildServerURL(address, settings.port));
|
|
57456
|
+
writeConnectURLs(env.stdout, serverURLs, { installed: true, token });
|
|
57457
|
+
};
|
|
57458
|
+
const serveInForeground = async (home, settings) => {
|
|
57256
57459
|
const profile = resolveACPAgentProfile(process.env);
|
|
57257
57460
|
if (profile && !isACPCommandResolvable(profile.command, process.env)) {
|
|
57258
57461
|
const error48 = createDirectiveError(
|
|
57259
57462
|
`Could not find ACP agent command \`${profile.command}\` for \`TELEVISION_ACP_AGENT=${profile.agent}\` on PATH. Set TELEVISION_ACP_AGENT to \`openclaw\` or \`hermes\`, and ensure the selected agent binary is available on PATH before running \`tv serve\`.`
|
|
57260
57463
|
);
|
|
57261
|
-
log(
|
|
57464
|
+
log(home, "server startup refused before binding", { argv, error: error48 });
|
|
57262
57465
|
throw error48;
|
|
57263
57466
|
}
|
|
57467
|
+
if (settings.port === 0) {
|
|
57468
|
+
writeLine(env.stderr, PORT_ZERO_WARNING);
|
|
57469
|
+
}
|
|
57264
57470
|
const onboardingContentPath = env.resolveOnboardingContentPath();
|
|
57265
57471
|
const bundledThemesPath = env.resolveBundledThemesPath();
|
|
57266
57472
|
const server = env.createServer({
|
|
57267
|
-
|
|
57268
|
-
|
|
57269
|
-
port:
|
|
57270
|
-
|
|
57473
|
+
home,
|
|
57474
|
+
listen: settings.listen,
|
|
57475
|
+
port: settings.port,
|
|
57476
|
+
auth: settings.auth,
|
|
57477
|
+
...settings.installedByAgent === void 0 ? {} : { installedByAgent: settings.installedByAgent },
|
|
57271
57478
|
staticDir: env.resolveStaticDir(),
|
|
57272
57479
|
canonicalDir: env.resolveCanonicalDir(),
|
|
57273
57480
|
bundledViewsPath: env.resolveBundledViewsPath(),
|
|
57274
57481
|
...onboardingContentPath !== void 0 ? { onboardingContentPath } : {},
|
|
57275
57482
|
...bundledThemesPath !== void 0 ? { bundledThemesPath } : {},
|
|
57276
57483
|
launchMode: resolveTelemetryLaunchMode(process.env),
|
|
57277
|
-
...opts.installedByAgent === void 0 ? {} : { installedByAgent: opts.installedByAgent },
|
|
57278
57484
|
...profile ? { acpProfile: profile } : {}
|
|
57279
57485
|
});
|
|
57280
57486
|
await server.start();
|
|
57487
|
+
if (!settings.auth) {
|
|
57488
|
+
writeLine(env.stderr, TOKENLESS_WARNING);
|
|
57489
|
+
const nonLoopbackAddresses = resolveBindAddresses(settings.listen).filter((address) => address !== LOOPBACK_IPV4);
|
|
57490
|
+
if (nonLoopbackAddresses.length > 0) {
|
|
57491
|
+
writeLine(env.stderr, `Non-loopback listeners without auth: ${nonLoopbackAddresses.join(", ")}`);
|
|
57492
|
+
}
|
|
57493
|
+
}
|
|
57281
57494
|
const serverURLs = server.getBaseURLs?.() ?? [server.getBaseURL()];
|
|
57282
57495
|
writeConnectURLs(env.stdout, serverURLs, {
|
|
57283
|
-
token: auth
|
|
57496
|
+
token: settings.auth ? server.getAuthToken() : null
|
|
57284
57497
|
});
|
|
57285
57498
|
await new Promise((resolve, reject) => {
|
|
57286
57499
|
let shuttingDown = false;
|
|
@@ -57301,10 +57514,61 @@ function createProgram(env, argv = []) {
|
|
|
57301
57514
|
void shutdown("SIGTERM");
|
|
57302
57515
|
});
|
|
57303
57516
|
});
|
|
57517
|
+
};
|
|
57518
|
+
const versionOutput = readCLIVersionOutput(env.resolveHomeDir);
|
|
57519
|
+
program2.name("tv").description("Television \u2014 virtual display for agents").version(versionOutput).option("-v", "Output the version number").option("--home <path>", "Television home directory (default: the path in ~/.tv-home, otherwise ~/.television)").showSuggestionAfterError(false).addHelpText("after", buildAgentHelpNote()).configureOutput({
|
|
57520
|
+
writeOut: (str) => env.stdout.write(str),
|
|
57521
|
+
writeErr: (str) => env.stderr.write(str),
|
|
57522
|
+
outputError: () => {
|
|
57523
|
+
}
|
|
57524
|
+
}).exitOverride();
|
|
57525
|
+
program2.on("option:v", () => {
|
|
57526
|
+
env.stdout.write(`${versionOutput}
|
|
57527
|
+
`);
|
|
57528
|
+
throw new CommanderError(0, "commander.version", versionOutput);
|
|
57529
|
+
});
|
|
57530
|
+
program2.command("serve").description("Start the Television server with the selected home's settings").option("--persist", "Install as a persistent system service").option("--persist-uninstall", "Uninstall the persistent system service").action(async (opts) => {
|
|
57531
|
+
if (opts.persist && opts.persistUninstall) {
|
|
57532
|
+
throw createDirectiveError("Use either `--persist` or `--persist-uninstall`, not both.");
|
|
57533
|
+
}
|
|
57534
|
+
const home = resolveHome();
|
|
57535
|
+
if (opts.persistUninstall) {
|
|
57536
|
+
await uninstallPersistedService(home);
|
|
57537
|
+
return;
|
|
57538
|
+
}
|
|
57539
|
+
let config2;
|
|
57540
|
+
try {
|
|
57541
|
+
if (invocation.retiredService) startRetiredService(home, invocation.retiredService);
|
|
57542
|
+
config2 = readTelevisionConfig(home);
|
|
57543
|
+
} catch (error48) {
|
|
57544
|
+
log(home, "server startup refused before binding", { argv, error: error48 });
|
|
57545
|
+
throw error48;
|
|
57546
|
+
}
|
|
57547
|
+
if (opts.persist) {
|
|
57548
|
+
await installPersistedService(home, config2);
|
|
57549
|
+
return;
|
|
57550
|
+
}
|
|
57551
|
+
await serveInForeground(home, config2.settings);
|
|
57552
|
+
});
|
|
57553
|
+
const configCommand = program2.command("config").description("Show the effective settings of the selected home, or change settings in its config file");
|
|
57554
|
+
configCommand.command("set").description("Change settings in the selected home's config file").argument("[pairs...]", `<key> <value> pairs; keys: ${CONFIG_KEYS2.join(", ")}`).action((pairs) => {
|
|
57555
|
+
const { keys, changes } = parseConfigSetPairs(pairs, formatEnteredInvocation(argv));
|
|
57556
|
+
const config2 = updateTelevisionConfig(resolveHome(), changes);
|
|
57557
|
+
writeLine(env.stdout, `Updated ${config2.configPath}: ${keys.join(", ")}.`);
|
|
57558
|
+
writeLine(env.stdout, "Restart foreground `tv serve`, or rerun `tv serve --persist`, for a running server to use the new settings.");
|
|
57559
|
+
});
|
|
57560
|
+
configCommand.command("show").description("Print the selected home, its config file, and the effective settings as JSON").action(() => {
|
|
57561
|
+
const config2 = readTelevisionConfig(resolveHome());
|
|
57562
|
+
writeJSON(env.stdout, {
|
|
57563
|
+
home: config2.home,
|
|
57564
|
+
configPath: config2.configPath,
|
|
57565
|
+
configFileExists: config2.configFileExists,
|
|
57566
|
+
settings: { ...config2.settings, installedByAgent: config2.settings.installedByAgent ?? null }
|
|
57567
|
+
});
|
|
57304
57568
|
});
|
|
57305
57569
|
program2.command("create-path-artifact").description(
|
|
57306
57570
|
"Create a path artifact that points at an existing absolute markdown or HTML file, or at a directory with a root index.html/index.htm (trailing separator optional; the server detects file vs directory on disk). Television records the pointer only; HTML and directory targets are read-only, and markdown targets are written through only by in-UI editor saves via PUT /markdown/<id>."
|
|
57307
|
-
).requiredOption("--channel <id>", "Target channel ID").requiredOption("--title <title>", "Artifact title").requiredOption("--path <path>", "Absolute path to an existing markdown/HTML file or indexed directory (trailing separator optional)").option("--focus-artifact", "Focus the new artifact after creation").option("--no-focus", "Create the artifact in the background without changing focus").option("--port <number>", "Server port
|
|
57571
|
+
).requiredOption("--channel <id>", "Target channel ID").requiredOption("--title <title>", "Artifact title").requiredOption("--path <path>", "Absolute path to an existing markdown/HTML file or indexed directory (trailing separator optional)").option("--focus-artifact", "Focus the new artifact after creation").option("--no-focus", "Create the artifact in the background without changing focus").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
|
|
57308
57572
|
const shouldFocus = resolveFocusDirective(argv, "create-path-artifact", "--focus-artifact");
|
|
57309
57573
|
const artifactPath = opts.path.trim();
|
|
57310
57574
|
const client = createAuthenticatedClient(opts);
|
|
@@ -57322,7 +57586,7 @@ function createProgram(env, argv = []) {
|
|
|
57322
57586
|
});
|
|
57323
57587
|
program2.command("create-url-artifact").description(
|
|
57324
57588
|
"Create a URL artifact that points at an absolute http(s) URL. Television records the pointer only and does not own or modify the remote page."
|
|
57325
|
-
).requiredOption("--channel <id>", "Target channel ID").requiredOption("--title <title>", "Artifact title").requiredOption("--url <url>", "http(s) URL").option("--focus-artifact", "Focus the new artifact after creation").option("--no-focus", "Create the artifact in the background without changing focus").option("--port <number>", "Server port
|
|
57589
|
+
).requiredOption("--channel <id>", "Target channel ID").requiredOption("--title <title>", "Artifact title").requiredOption("--url <url>", "http(s) URL").option("--focus-artifact", "Focus the new artifact after creation").option("--no-focus", "Create the artifact in the background without changing focus").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).addHelpText("after", buildArtifactWorkflowHelpNote(true)).action(async (opts) => {
|
|
57326
57590
|
const shouldFocus = resolveFocusDirective(argv, "create-url-artifact", "--focus-artifact");
|
|
57327
57591
|
const artifactURL = opts.url.trim();
|
|
57328
57592
|
const client = createAuthenticatedClient(opts);
|
|
@@ -57338,12 +57602,9 @@ function createProgram(env, argv = []) {
|
|
|
57338
57602
|
writeLine(env.stdout, `URL artifact ${artifact.id} created.`);
|
|
57339
57603
|
writeLine(env.stdout, `Television registered ${artifactURL}.`);
|
|
57340
57604
|
});
|
|
57341
|
-
program2.command("storage-path").description("Print the Television storage path").action(() => {
|
|
57342
|
-
writeJSON(env.stdout, { storagePath: getTelevisionStoragePath() });
|
|
57343
|
-
});
|
|
57344
57605
|
program2.command("update-artifact").description(
|
|
57345
57606
|
"Update artifact metadata in place: --title on any artifact, --path to repoint a path artifact, --url to repoint a URL artifact. ID and kind are immutable. The server validates the new pointer; for path artifacts the target must exist (trailing separator optional) and the content watcher follows it."
|
|
57346
|
-
).requiredOption("--id <id>", "Artifact ID").option("--title <title>", "New title").option("--path <path>", "New absolute path for a path artifact: markdown/HTML file or indexed directory (trailing separator optional)").option("--url <url>", "New http(s) URL for a URL artifact").option("--port <number>", "Server port
|
|
57607
|
+
).requiredOption("--id <id>", "Artifact ID").option("--title <title>", "New title").option("--path <path>", "New absolute path for a path artifact: markdown/HTML file or indexed directory (trailing separator optional)").option("--url <url>", "New http(s) URL for a URL artifact").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57347
57608
|
if (opts.title === void 0 && opts.path === void 0 && opts.url === void 0) {
|
|
57348
57609
|
throw createDirectiveError("tv update-artifact requires at least one of --title, --path, or --url.");
|
|
57349
57610
|
}
|
|
@@ -57361,20 +57622,20 @@ function createProgram(env, argv = []) {
|
|
|
57361
57622
|
});
|
|
57362
57623
|
program2.command("delete-artifact").description(
|
|
57363
57624
|
"Delete an artifact registry record, remove its card from its owning channel, and delete its metadata. Television never removes or mutates the pointed-to path or URL."
|
|
57364
|
-
).requiredOption("--id <id>", "Artifact ID").option("--port <number>", "Server port
|
|
57625
|
+
).requiredOption("--id <id>", "Artifact ID").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57365
57626
|
const client = createAuthenticatedClient(opts);
|
|
57366
57627
|
const result = await client.artifacts.delete({ artifactID: opts.id });
|
|
57367
57628
|
for (const line of formatDeleteArtifactResult(result)) {
|
|
57368
57629
|
writeLine(env.stdout, line);
|
|
57369
57630
|
}
|
|
57370
57631
|
});
|
|
57371
|
-
program2.command("get-artifact").description("Fetch an artifact's metadata as JSON").requiredOption("--id <id>", "Artifact ID").option("--port <number>", "Server port
|
|
57632
|
+
program2.command("get-artifact").description("Fetch an artifact's metadata as JSON").requiredOption("--id <id>", "Artifact ID").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57372
57633
|
const client = createAuthenticatedClient(opts);
|
|
57373
57634
|
writeJSON(env.stdout, await client.artifacts.get({ artifactID: opts.id }));
|
|
57374
57635
|
});
|
|
57375
57636
|
program2.command("list-artifacts").description(
|
|
57376
57637
|
"List artifacts as JSON. With no filter, every artifact is returned. --channel <id> filters to artifacts on that channel."
|
|
57377
|
-
).option("--channel <id>", "Filter to artifacts on this channel").option("--port <number>", "Server port
|
|
57638
|
+
).option("--channel <id>", "Filter to artifacts on this channel").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57378
57639
|
const client = createAuthenticatedClient(opts);
|
|
57379
57640
|
const filter = {};
|
|
57380
57641
|
if (opts.channel !== void 0) filter.channelID = opts.channel;
|
|
@@ -57382,7 +57643,7 @@ function createProgram(env, argv = []) {
|
|
|
57382
57643
|
});
|
|
57383
57644
|
program2.command("create-channel").description(
|
|
57384
57645
|
"Create a new channel. Pick --focus-channel when the user should be taken to the new channel immediately (creating a workspace they're about to use), or --no-focus to create it in the background without disturbing their current view. A new channel starts empty; create new path or URL artifacts on it with the create-*-artifact commands using --channel."
|
|
57385
|
-
).requiredOption("--name <name>", "Channel name").option("--focus-channel", "Focus the new channel after creating it").option("--no-focus", "Create the channel in the background without changing focus").option("--port <number>", "Server port
|
|
57646
|
+
).requiredOption("--name <name>", "Channel name").option("--focus-channel", "Focus the new channel after creating it").option("--no-focus", "Create the channel in the background without changing focus").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57386
57647
|
const shouldFocus = resolveFocusDirective(argv, "create-channel", "--focus-channel");
|
|
57387
57648
|
const client = createAuthenticatedClient(opts);
|
|
57388
57649
|
const { channel } = await client.channels.create({ name: opts.name });
|
|
@@ -57391,7 +57652,7 @@ function createProgram(env, argv = []) {
|
|
|
57391
57652
|
}
|
|
57392
57653
|
writeLine(env.stdout, `Channel created: ${channel.id} (${channel.name})`);
|
|
57393
57654
|
});
|
|
57394
|
-
program2.command("update-channel").description("Rename a channel in place").requiredOption("--channel <id>", "Channel ID").requiredOption("--name <name>", "New channel name").option("--port <number>", "Server port
|
|
57655
|
+
program2.command("update-channel").description("Rename a channel in place").requiredOption("--channel <id>", "Channel ID").requiredOption("--name <name>", "New channel name").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57395
57656
|
const client = createAuthenticatedClient(opts);
|
|
57396
57657
|
const { channel } = await client.channels.update({
|
|
57397
57658
|
channelID: opts.channel,
|
|
@@ -57401,26 +57662,26 @@ function createProgram(env, argv = []) {
|
|
|
57401
57662
|
});
|
|
57402
57663
|
program2.command("remove-channel").description(
|
|
57403
57664
|
"Delete a channel. Artifacts on the channel are permanently deleted from the Television registry; their pointed-to path or URL targets are not touched."
|
|
57404
|
-
).requiredOption("--channel <id>", "Channel ID").option("--port <number>", "Server port
|
|
57665
|
+
).requiredOption("--channel <id>", "Channel ID").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57405
57666
|
const client = createAuthenticatedClient(opts);
|
|
57406
57667
|
const result = await client.channels.remove({ channelID: opts.channel });
|
|
57407
57668
|
for (const line of formatChannelRemovalResult(result)) {
|
|
57408
57669
|
writeLine(env.stdout, line);
|
|
57409
57670
|
}
|
|
57410
57671
|
});
|
|
57411
|
-
program2.command("list-channels").description("List all channels").option("--port <number>", "Server port
|
|
57672
|
+
program2.command("list-channels").description("List all channels").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57412
57673
|
const client = createAuthenticatedClient(opts);
|
|
57413
57674
|
writeJSON(env.stdout, await client.channels.list());
|
|
57414
57675
|
});
|
|
57415
57676
|
program2.command("get-channel").description(
|
|
57416
57677
|
"Get a channel and its path/url artifact records as JSON. With no --channel, auto-selects when exactly one channel exists; otherwise --channel is required."
|
|
57417
|
-
).option("--channel <id>", "Channel ID (auto-selects if only one exists)").option("--port <number>", "Server port
|
|
57678
|
+
).option("--channel <id>", "Channel ID (auto-selects if only one exists)").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57418
57679
|
const client = createAuthenticatedClient(opts);
|
|
57419
57680
|
writeJSON(env.stdout, await client.channels.get({ channelID: opts.channel }));
|
|
57420
57681
|
});
|
|
57421
57682
|
program2.command("focus-status").description(
|
|
57422
57683
|
"Print display state as JSON: the active (persistently focused) channel ID and the active theme ID."
|
|
57423
|
-
).option("--port <number>", "Server port
|
|
57684
|
+
).option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57424
57685
|
const client = createAuthenticatedClient(opts);
|
|
57425
57686
|
const display = await client.display.get();
|
|
57426
57687
|
writeJSON(env.stdout, {
|
|
@@ -57431,14 +57692,14 @@ function createProgram(env, argv = []) {
|
|
|
57431
57692
|
});
|
|
57432
57693
|
program2.command("focus-channel").description(
|
|
57433
57694
|
"Set persistent channel focus. The change is broadcast to every connected GUI client and survives reconnects. This is the right command when the user wants to switch to a different channel and stay there. For just nudging attention to a specific artifact (which may live on the current channel), prefer `tv focus-artifact`."
|
|
57434
|
-
).requiredOption("--channel <id>", "Channel ID").option("--port <number>", "Server port
|
|
57695
|
+
).requiredOption("--channel <id>", "Channel ID").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57435
57696
|
const client = createAuthenticatedClient(opts);
|
|
57436
57697
|
await client.display.patch({ focusedChannelId: opts.channel });
|
|
57437
57698
|
writeLine(env.stdout, `Focused channel ${opts.channel}.`);
|
|
57438
57699
|
});
|
|
57439
57700
|
program2.command("set-theme").description(
|
|
57440
57701
|
"Activate an installed display theme by its exact theme ID, or use `none` to run without a theme."
|
|
57441
|
-
).argument("<theme>", "Exact theme ID to activate, or `none` to use no theme").option("--port <number>", "Server port
|
|
57702
|
+
).argument("<theme>", "Exact theme ID to activate, or `none` to use no theme").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).addHelpText("after", [
|
|
57442
57703
|
"",
|
|
57443
57704
|
"Examples:",
|
|
57444
57705
|
" tv set-theme paperlike Activate the `paperlike` theme.",
|
|
@@ -57476,9 +57737,12 @@ function createProgram(env, argv = []) {
|
|
|
57476
57737
|
}
|
|
57477
57738
|
writeLine(env.stdout, `Active theme changed from '${previousLabel}' to '${nextLabel}'.`);
|
|
57478
57739
|
});
|
|
57740
|
+
program2.command("themes-path").description("Print the themes directory of the selected home as JSON").action(() => {
|
|
57741
|
+
writeJSON(env.stdout, { themesPath: import_node_path20.default.join(resolveHome(), "themes") });
|
|
57742
|
+
});
|
|
57479
57743
|
program2.command("focus-artifact").description(
|
|
57480
57744
|
"Send a transient focus nudge for a specific artifact. Connected clients select the artifact's tab page and switch to its channel when needed. This is NOT persisted as state \u2014 there is no concept of a 'focused artifact' that survives reconnects (the focused channel is persistent, but artifact focus is a one-shot event)."
|
|
57481
|
-
).requiredOption("--id <id>", "Artifact ID").option("--port <number>", "Server port
|
|
57745
|
+
).requiredOption("--id <id>", "Artifact ID").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57482
57746
|
const client = createAuthenticatedClient(opts);
|
|
57483
57747
|
const result = await client.display.focus({ artifactID: opts.id });
|
|
57484
57748
|
writeLine(env.stdout, `Focused artifact ${result.artifactID} on channel ${result.channelID}.`);
|
|
@@ -57491,6 +57755,7 @@ function createProgram(env, argv = []) {
|
|
|
57491
57755
|
"`tv skills install -i` runs the external skills installer against Television's bundled skills root."
|
|
57492
57756
|
].join("\n"));
|
|
57493
57757
|
skillsCommand.command("install").description("Copy bundled Television skills into an agent harness skills folder, or run the external installer against the bundled skills root").argument("[path]", "Destination agent skills folder (for example: ~/.openclaw/skills, ~/.hermes/skills, ~/.agents/skills)").option("-i, --interactive", "Run the external skills installer against the bundled Television skills root").option("--installed-by-agent <agent-runtime-harness-name>", "Agent runtime harness name performing this install").action(async function(inputPath, opts) {
|
|
57758
|
+
const home = resolveHome();
|
|
57494
57759
|
const bundledTelevisionSkillsCollectionRoot = env.resolveBundledSkillsRoot();
|
|
57495
57760
|
if (opts.interactive) {
|
|
57496
57761
|
if (inputPath) {
|
|
@@ -57502,6 +57767,7 @@ function createProgram(env, argv = []) {
|
|
|
57502
57767
|
const args = ["add", bundledTelevisionSkillsCollectionRoot];
|
|
57503
57768
|
await env.runSkillsInstaller(args);
|
|
57504
57769
|
await emitSkillInstallTelemetry({
|
|
57770
|
+
home,
|
|
57505
57771
|
agentType: "interactive-install",
|
|
57506
57772
|
...opts.installedByAgent === void 0 ? {} : { installedByAgent: opts.installedByAgent }
|
|
57507
57773
|
});
|
|
@@ -57524,30 +57790,33 @@ function createProgram(env, argv = []) {
|
|
|
57524
57790
|
writeLine(env.stdout, ` to: ${skill.destinationPath}`);
|
|
57525
57791
|
}
|
|
57526
57792
|
await emitSkillInstallTelemetry({
|
|
57793
|
+
home,
|
|
57527
57794
|
agentType: deriveAgentTypeFromPath(destinationRoot).agent_type,
|
|
57528
57795
|
...opts.installedByAgent === void 0 ? {} : { installedByAgent: opts.installedByAgent }
|
|
57529
57796
|
});
|
|
57530
57797
|
});
|
|
57531
57798
|
const telemetryCommand = program2.command("telemetry").description("Control telemetry for the running Television server");
|
|
57532
|
-
telemetryCommand.command("disable").description("Disable telemetry for the running Television server").option("--port <number>", "Server port
|
|
57799
|
+
telemetryCommand.command("disable").description("Disable telemetry for the running Television server").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57533
57800
|
const client = createAuthenticatedClient(opts);
|
|
57534
57801
|
writeJSON(env.stdout, await client.telemetry.disable());
|
|
57535
57802
|
});
|
|
57536
|
-
telemetryCommand.command("enable").description("Enable telemetry for the running Television server").option("--port <number>", "Server port
|
|
57803
|
+
telemetryCommand.command("enable").description("Enable telemetry for the running Television server").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57537
57804
|
const client = createAuthenticatedClient(opts);
|
|
57538
57805
|
writeJSON(env.stdout, await client.telemetry.enable());
|
|
57539
57806
|
});
|
|
57540
57807
|
program2.command("stop").description("Stop the Television system service").action(async () => {
|
|
57541
|
-
await uninstallPersistedService(
|
|
57808
|
+
await uninstallPersistedService(resolveHome());
|
|
57542
57809
|
});
|
|
57543
|
-
program2.command("status").description("
|
|
57544
|
-
const
|
|
57810
|
+
program2.command("status").description("Print the selected home, server health, version, telemetry state, and service status as JSON").option("--port <number>", "Server port; required when the config file sets port 0", parseClientPortOption).action(async (opts) => {
|
|
57811
|
+
const home = resolveHome();
|
|
57812
|
+
const serverURL = resolveServerURL(opts.port);
|
|
57545
57813
|
const result = {
|
|
57814
|
+
home,
|
|
57546
57815
|
serverURL,
|
|
57547
57816
|
healthy: false
|
|
57548
57817
|
};
|
|
57549
57818
|
try {
|
|
57550
|
-
const client =
|
|
57819
|
+
const client = env.createClient(serverURL, readAuthToken(home));
|
|
57551
57820
|
const health = await client.health();
|
|
57552
57821
|
result.healthy = true;
|
|
57553
57822
|
if (health.version !== void 0) result.version = health.version;
|
|
@@ -57572,17 +57841,25 @@ function listVisibleCLICommandNames() {
|
|
|
57572
57841
|
}
|
|
57573
57842
|
async function runCLI(argv, environment = {}) {
|
|
57574
57843
|
const env = createEnvironment(environment);
|
|
57844
|
+
const { command, retired, remaining } = extractRetiredOptions(argv);
|
|
57845
|
+
const retiredService = retired.length > 0 ? readRetiredServiceDefinition(command, retired, remaining, process.env) : void 0;
|
|
57846
|
+
if (retired.length > 0 && !retiredService) {
|
|
57847
|
+
const named = [...new Set(retired.map((option) => option.name))].join(", ");
|
|
57848
|
+
env.stderr.write(`${ensureHelpPointer(`This version of Television does not accept ${named}. ${RETIRED_OPTIONS_GUIDANCE}`)}
|
|
57849
|
+
`);
|
|
57850
|
+
return 1;
|
|
57851
|
+
}
|
|
57852
|
+
if (process.env[TELEMETRY_LAUNCH_MODE_ENV] !== "daemon" && (process.env.TELEVISION_PORT || process.env.TELEVISION_STORAGE_PATH)) {
|
|
57853
|
+
writeLine(env.stderr, LEGACY_VARIABLES_WARNING);
|
|
57854
|
+
}
|
|
57855
|
+
const invocation = { argv, ...retiredService ? { retiredService } : {} };
|
|
57575
57856
|
try {
|
|
57576
|
-
|
|
57577
|
-
writeLine(env.stdout, readCLIVersionOutput(env.resolveHomeDir));
|
|
57578
|
-
return 0;
|
|
57579
|
-
}
|
|
57580
|
-
const program2 = createProgram(env, argv);
|
|
57857
|
+
const program2 = createProgram(env, invocation);
|
|
57581
57858
|
if (argv.length === 0) {
|
|
57582
57859
|
program2.outputHelp();
|
|
57583
57860
|
return 0;
|
|
57584
57861
|
}
|
|
57585
|
-
await program2.parseAsync(
|
|
57862
|
+
await program2.parseAsync(remaining, { from: "user" });
|
|
57586
57863
|
return 0;
|
|
57587
57864
|
} catch (error48) {
|
|
57588
57865
|
if (error48 instanceof Error && "exitCode" in error48) {
|
|
@@ -57596,7 +57873,7 @@ async function runCLI(argv, environment = {}) {
|
|
|
57596
57873
|
}
|
|
57597
57874
|
return 1;
|
|
57598
57875
|
}
|
|
57599
|
-
env.stderr.write(`${formatCLIError(error48,
|
|
57876
|
+
env.stderr.write(`${formatCLIError(error48, invocation)}
|
|
57600
57877
|
`);
|
|
57601
57878
|
return exitStatusFromError(error48) ?? 1;
|
|
57602
57879
|
}
|