@slayzone/cli 0.36.0-beta.10 → 0.36.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/slay.js +864 -239
- package/package.json +1 -1
package/dist/slay.js
CHANGED
|
@@ -7248,7 +7248,7 @@ var require_BufferList = __commonJS({
|
|
|
7248
7248
|
this.head = this.tail = null;
|
|
7249
7249
|
this.length = 0;
|
|
7250
7250
|
};
|
|
7251
|
-
BufferList.prototype.join = function
|
|
7251
|
+
BufferList.prototype.join = function join7(s) {
|
|
7252
7252
|
if (this.length === 0) return "";
|
|
7253
7253
|
var p2 = this.head;
|
|
7254
7254
|
var ret = "" + p2.data;
|
|
@@ -46794,9 +46794,13 @@ complete -c slay -n '__fish_use_subcommand' -a 'tags' -d 'Manage tags'
|
|
|
46794
46794
|
complete -c slay -n '__fish_use_subcommand' -a 'templates' -d 'Manage task templates'
|
|
46795
46795
|
complete -c slay -n '__fish_use_subcommand' -a 'automations' -d 'Manage automations'
|
|
46796
46796
|
complete -c slay -n '__fish_use_subcommand' -a 'processes' -d 'Manage processes'
|
|
46797
|
+
complete -c slay -n '__fish_use_subcommand' -a 'hub' -d 'Run, list and target SlayZone hubs'
|
|
46798
|
+
complete -c slay -n '__fish_use_subcommand' -a 'runner' -d 'Run and manage SlayZone runners'
|
|
46797
46799
|
complete -c slay -n '__fish_use_subcommand' -a 'completions' -d 'Print shell completions'
|
|
46798
46800
|
complete -c slay -n '__fish_seen_subcommand_from tasks' -a 'list create view done update archive delete open search subtasks subtask-add tag'
|
|
46799
46801
|
complete -c slay -n '__fish_seen_subcommand_from processes' -a 'list logs kill follow'
|
|
46802
|
+
complete -c slay -n '__fish_seen_subcommand_from hub' -a 'ls create start stop rm restart logs registered use current forget'
|
|
46803
|
+
complete -c slay -n '__fish_seen_subcommand_from runner' -a 'ls create start stop rm restart logs'
|
|
46800
46804
|
complete -c slay -n '__fish_seen_subcommand_from projects' -a 'list create update'
|
|
46801
46805
|
complete -c slay -n '__fish_seen_subcommand_from tags' -a 'list create delete'
|
|
46802
46806
|
complete -c slay -n '__fish_seen_subcommand_from templates' -a 'list view create update delete'
|
|
@@ -46814,6 +46818,8 @@ _slay() {
|
|
|
46814
46818
|
'templates:Manage task templates'
|
|
46815
46819
|
'automations:Manage automations'
|
|
46816
46820
|
'processes:Manage processes'
|
|
46821
|
+
'hub:Run, list and target SlayZone hubs'
|
|
46822
|
+
'runner:Run and manage SlayZone runners'
|
|
46817
46823
|
'completions:Print shell completions'
|
|
46818
46824
|
)
|
|
46819
46825
|
local -a task_commands
|
|
@@ -46863,6 +46869,30 @@ _slay() {
|
|
|
46863
46869
|
'run:Run automation manually'
|
|
46864
46870
|
'runs:View execution history'
|
|
46865
46871
|
)
|
|
46872
|
+
local -a hub_commands
|
|
46873
|
+
hub_commands=(
|
|
46874
|
+
'ls:List running hubs'
|
|
46875
|
+
'create:Create a hub here and keep it running'
|
|
46876
|
+
'start:Start a stopped hub'
|
|
46877
|
+
'stop:Stop a hub, keeping it registered'
|
|
46878
|
+
'rm:Stop a hub and remove its registration'
|
|
46879
|
+
'restart:Restart a hub'
|
|
46880
|
+
'logs:Show a hub log'
|
|
46881
|
+
'registered:List hubs registered with the OS service manager'
|
|
46882
|
+
'use:Point this CLI at a hub'
|
|
46883
|
+
'current:Show which hub this CLI targets'
|
|
46884
|
+
'forget:Drop the stored hub target'
|
|
46885
|
+
)
|
|
46886
|
+
local -a runner_commands
|
|
46887
|
+
runner_commands=(
|
|
46888
|
+
'ls:List installed runners'
|
|
46889
|
+
'create:Install a runner here and keep it running'
|
|
46890
|
+
'start:Start a stopped runner'
|
|
46891
|
+
'stop:Stop a runner, keeping it registered'
|
|
46892
|
+
'rm:Stop a runner and remove its registration'
|
|
46893
|
+
'restart:Restart a runner'
|
|
46894
|
+
'logs:Show a runner log'
|
|
46895
|
+
)
|
|
46866
46896
|
case $words[2] in
|
|
46867
46897
|
tasks)
|
|
46868
46898
|
_describe 'task commands' task_commands
|
|
@@ -46882,6 +46912,12 @@ _slay() {
|
|
|
46882
46912
|
automations)
|
|
46883
46913
|
_describe 'automation commands' automation_commands
|
|
46884
46914
|
;;
|
|
46915
|
+
hub)
|
|
46916
|
+
_describe 'hub commands' hub_commands
|
|
46917
|
+
;;
|
|
46918
|
+
runner)
|
|
46919
|
+
_describe 'runner commands' runner_commands
|
|
46920
|
+
;;
|
|
46885
46921
|
completions)
|
|
46886
46922
|
_arguments ':shell:(fish zsh bash)'
|
|
46887
46923
|
;;
|
|
@@ -46899,7 +46935,7 @@ _slay_completions() {
|
|
|
46899
46935
|
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
46900
46936
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
46901
46937
|
if [ $COMP_CWORD -eq 1 ]; then
|
|
46902
|
-
COMPREPLY=( $(compgen -W "tasks projects tags templates automations processes completions" -- "$cur") )
|
|
46938
|
+
COMPREPLY=( $(compgen -W "tasks projects tags templates automations processes hub runner completions" -- "$cur") )
|
|
46903
46939
|
elif [ $COMP_CWORD -eq 2 ]; then
|
|
46904
46940
|
case "$prev" in
|
|
46905
46941
|
tasks)
|
|
@@ -46920,6 +46956,12 @@ _slay_completions() {
|
|
|
46920
46956
|
automations)
|
|
46921
46957
|
COMPREPLY=( $(compgen -W "list view create update delete toggle run runs" -- "$cur") )
|
|
46922
46958
|
;;
|
|
46959
|
+
hub)
|
|
46960
|
+
COMPREPLY=( $(compgen -W "ls create start stop rm restart logs registered use current forget" -- "$cur") )
|
|
46961
|
+
;;
|
|
46962
|
+
runner)
|
|
46963
|
+
COMPREPLY=( $(compgen -W "ls create start stop rm restart logs" -- "$cur") )
|
|
46964
|
+
;;
|
|
46923
46965
|
completions)
|
|
46924
46966
|
COMPREPLY=( $(compgen -W "fish zsh bash" -- "$cur") )
|
|
46925
46967
|
;;
|
|
@@ -51987,25 +52029,45 @@ function panelsCommand() {
|
|
|
51987
52029
|
}
|
|
51988
52030
|
|
|
51989
52031
|
// src/commands/hub.ts
|
|
51990
|
-
var
|
|
51991
|
-
var
|
|
51992
|
-
var
|
|
51993
|
-
var import_node_path6 = require("node:path");
|
|
52032
|
+
var import_node_child_process3 = require("node:child_process");
|
|
52033
|
+
var import_node_fs7 = require("node:fs");
|
|
52034
|
+
var import_node_path7 = require("node:path");
|
|
51994
52035
|
|
|
51995
|
-
// ../../shared/platform/src/
|
|
52036
|
+
// ../../shared/platform/src/service-unit.ts
|
|
51996
52037
|
var import_node_child_process = require("node:child_process");
|
|
51997
52038
|
var import_node_fs5 = require("node:fs");
|
|
51998
52039
|
var import_node_os = require("node:os");
|
|
51999
52040
|
var import_node_path5 = require("node:path");
|
|
52000
|
-
var
|
|
52001
|
-
|
|
52002
|
-
|
|
52041
|
+
var KINDS = {
|
|
52042
|
+
hub: {
|
|
52043
|
+
labelPrefix: "com.slayzone.hub.",
|
|
52044
|
+
systemdPrefix: "slayzone-hub-",
|
|
52045
|
+
description: "SlayZone hub",
|
|
52046
|
+
/** launchd stdout/stderr basenames (systemd uses journald). */
|
|
52047
|
+
outLog: "hub.out.log",
|
|
52048
|
+
errLog: "hub.err.log",
|
|
52049
|
+
/** The `slay … logs <name>` command, quoted in the systemd unit's comment. */
|
|
52050
|
+
logsCommand: "slay hub logs",
|
|
52051
|
+
/** How an operator picks a different name — quoted in the invalid-name error. */
|
|
52052
|
+
renameHint: "Rename with `--name` or the config.json `hubName` key."
|
|
52053
|
+
},
|
|
52054
|
+
runner: {
|
|
52055
|
+
labelPrefix: "com.slayzone.runner.",
|
|
52056
|
+
systemdPrefix: "slayzone-runner-",
|
|
52057
|
+
description: "SlayZone runner",
|
|
52058
|
+
outLog: "runner.out.log",
|
|
52059
|
+
errLog: "runner.err.log",
|
|
52060
|
+
logsCommand: "slay runner logs",
|
|
52061
|
+
renameHint: "Pick a different name for `slay runner create`."
|
|
52062
|
+
}
|
|
52063
|
+
};
|
|
52064
|
+
function isValidServiceName(name) {
|
|
52003
52065
|
return /^[A-Za-z0-9_][A-Za-z0-9._-]*$/.test(name);
|
|
52004
52066
|
}
|
|
52005
|
-
function
|
|
52006
|
-
if (!
|
|
52067
|
+
function assertValidServiceName(kind, name) {
|
|
52068
|
+
if (!isValidServiceName(name)) {
|
|
52007
52069
|
throw new Error(
|
|
52008
|
-
`[slayzone] invalid
|
|
52070
|
+
`[slayzone] invalid ${kind} name ${JSON.stringify(name)} \u2014 a name may contain only letters, digits, dot, dash and underscore (no slashes, spaces or newlines), and must not start with a dot. ${KINDS[kind].renameHint}`
|
|
52009
52071
|
);
|
|
52010
52072
|
}
|
|
52011
52073
|
}
|
|
@@ -52031,41 +52093,44 @@ function detectBackend() {
|
|
|
52031
52093
|
return "none";
|
|
52032
52094
|
}
|
|
52033
52095
|
}
|
|
52034
|
-
function
|
|
52035
|
-
|
|
52036
|
-
return backend === "launchd" ? (0, import_node_path5.join)(unitDir, `${
|
|
52096
|
+
function unitPath(kind, name, backend, unitDir = defaultUnitDir(backend)) {
|
|
52097
|
+
assertValidServiceName(kind, name);
|
|
52098
|
+
return backend === "launchd" ? (0, import_node_path5.join)(unitDir, `${KINDS[kind].labelPrefix}${name}.plist`) : (0, import_node_path5.join)(unitDir, `${KINDS[kind].systemdPrefix}${name}.service`);
|
|
52037
52099
|
}
|
|
52038
|
-
function nameFromUnitFile(file2, backend) {
|
|
52100
|
+
function nameFromUnitFile(kind, file2, backend) {
|
|
52039
52101
|
if (backend === "launchd") {
|
|
52040
|
-
|
|
52041
|
-
|
|
52102
|
+
const prefix2 = KINDS[kind].labelPrefix;
|
|
52103
|
+
if (!file2.startsWith(prefix2) || !file2.endsWith(".plist")) return null;
|
|
52104
|
+
return file2.slice(prefix2.length, -".plist".length) || null;
|
|
52042
52105
|
}
|
|
52043
|
-
|
|
52044
|
-
|
|
52106
|
+
const prefix = KINDS[kind].systemdPrefix;
|
|
52107
|
+
if (!file2.startsWith(prefix) || !file2.endsWith(".service")) return null;
|
|
52108
|
+
return file2.slice(prefix.length, -".service".length) || null;
|
|
52045
52109
|
}
|
|
52046
52110
|
function xmlEscape(value) {
|
|
52047
52111
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
52048
52112
|
}
|
|
52049
|
-
function
|
|
52050
|
-
const merged = {
|
|
52051
|
-
|
|
52052
|
-
SLAYZONE_HUB_NAME
|
|
52053
|
-
|
|
52054
|
-
|
|
52113
|
+
function unitEnv(spec) {
|
|
52114
|
+
const merged = { SLAYZONE_ROOT: spec.root };
|
|
52115
|
+
if (spec.kind === "hub") {
|
|
52116
|
+
merged.SLAYZONE_HUB_NAME = spec.name;
|
|
52117
|
+
if (spec.port !== void 0) merged.SLAYZONE_HUB_ADDRESS = `127.0.0.1:${spec.port}`;
|
|
52118
|
+
}
|
|
52055
52119
|
Object.assign(merged, spec.env ?? {});
|
|
52056
52120
|
return Object.entries(merged);
|
|
52057
52121
|
}
|
|
52058
52122
|
function renderLaunchdPlist(spec) {
|
|
52059
|
-
|
|
52123
|
+
assertValidServiceName(spec.kind, spec.name);
|
|
52124
|
+
const kind = KINDS[spec.kind];
|
|
52060
52125
|
const programArgs = [spec.command, ...spec.args].map((a) => ` <string>${xmlEscape(a)}</string>`).join("\n");
|
|
52061
|
-
const envEntries =
|
|
52126
|
+
const envEntries = unitEnv(spec).map(([k, v2]) => ` <key>${xmlEscape(k)}</key>
|
|
52062
52127
|
<string>${xmlEscape(v2)}</string>`).join("\n");
|
|
52063
52128
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
52064
52129
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
52065
52130
|
<plist version="1.0">
|
|
52066
52131
|
<dict>
|
|
52067
52132
|
<key>Label</key>
|
|
52068
|
-
<string>${
|
|
52133
|
+
<string>${kind.labelPrefix}${spec.name}</string>
|
|
52069
52134
|
<key>ProgramArguments</key>
|
|
52070
52135
|
<array>
|
|
52071
52136
|
${programArgs}
|
|
@@ -52084,9 +52149,9 @@ ${envEntries}
|
|
|
52084
52149
|
<false/>
|
|
52085
52150
|
</dict>
|
|
52086
52151
|
<key>StandardOutPath</key>
|
|
52087
|
-
<string>${xmlEscape((0, import_node_path5.join)(spec.logDir,
|
|
52152
|
+
<string>${xmlEscape((0, import_node_path5.join)(spec.logDir, kind.outLog))}</string>
|
|
52088
52153
|
<key>StandardErrorPath</key>
|
|
52089
|
-
<string>${xmlEscape((0, import_node_path5.join)(spec.logDir,
|
|
52154
|
+
<string>${xmlEscape((0, import_node_path5.join)(spec.logDir, kind.errLog))}</string>
|
|
52090
52155
|
<key>ProcessType</key>
|
|
52091
52156
|
<string>Background</string>
|
|
52092
52157
|
</dict>
|
|
@@ -52094,11 +52159,12 @@ ${envEntries}
|
|
|
52094
52159
|
`;
|
|
52095
52160
|
}
|
|
52096
52161
|
function renderSystemdUnit(spec) {
|
|
52097
|
-
|
|
52162
|
+
assertValidServiceName(spec.kind, spec.name);
|
|
52163
|
+
const kind = KINDS[spec.kind];
|
|
52098
52164
|
const exec = [spec.command, ...spec.args].join(" ");
|
|
52099
|
-
const envLines =
|
|
52165
|
+
const envLines = unitEnv(spec).map(([k, v2]) => `Environment=${k}=${v2}`).join("\n");
|
|
52100
52166
|
return `[Unit]
|
|
52101
|
-
Description
|
|
52167
|
+
Description=${kind.description} (${spec.name})
|
|
52102
52168
|
Documentation=https://github.com/debuglebowski/slayzone
|
|
52103
52169
|
After=network.target
|
|
52104
52170
|
|
|
@@ -52109,7 +52175,7 @@ ${envLines}
|
|
|
52109
52175
|
ExecStart=${exec}
|
|
52110
52176
|
Restart=on-failure
|
|
52111
52177
|
RestartSec=2
|
|
52112
|
-
# stdout/stderr go to journald; read them with
|
|
52178
|
+
# stdout/stderr go to journald; read them with \`${kind.logsCommand} ${spec.name}\`.
|
|
52113
52179
|
StandardOutput=journal
|
|
52114
52180
|
StandardError=journal
|
|
52115
52181
|
|
|
@@ -52117,20 +52183,20 @@ StandardError=journal
|
|
|
52117
52183
|
WantedBy=default.target
|
|
52118
52184
|
`;
|
|
52119
52185
|
}
|
|
52120
|
-
function
|
|
52121
|
-
const path10 =
|
|
52186
|
+
function writeUnit(spec, backend, unitDir = defaultUnitDir(backend)) {
|
|
52187
|
+
const path10 = unitPath(spec.kind, spec.name, backend, unitDir);
|
|
52122
52188
|
(0, import_node_fs5.mkdirSync)(unitDir, { recursive: true });
|
|
52123
52189
|
const content = backend === "launchd" ? renderLaunchdPlist(spec) : renderSystemdUnit(spec);
|
|
52124
52190
|
(0, import_node_fs5.writeFileSync)(path10, content, { mode: 420 });
|
|
52125
52191
|
return path10;
|
|
52126
52192
|
}
|
|
52127
|
-
function
|
|
52128
|
-
const path10 =
|
|
52193
|
+
function removeUnit(kind, name, backend, unitDir = defaultUnitDir(backend)) {
|
|
52194
|
+
const path10 = unitPath(kind, name, backend, unitDir);
|
|
52129
52195
|
if (!(0, import_node_fs5.existsSync)(path10)) return false;
|
|
52130
52196
|
(0, import_node_fs5.rmSync)(path10, { force: true });
|
|
52131
52197
|
return true;
|
|
52132
52198
|
}
|
|
52133
|
-
function
|
|
52199
|
+
function listRegisteredUnits(kind, backend, unitDir = defaultUnitDir(backend)) {
|
|
52134
52200
|
let files;
|
|
52135
52201
|
try {
|
|
52136
52202
|
files = (0, import_node_fs5.readdirSync)(unitDir);
|
|
@@ -52139,15 +52205,15 @@ function listRegisteredHubs(backend, unitDir = defaultUnitDir(backend)) {
|
|
|
52139
52205
|
}
|
|
52140
52206
|
const out = [];
|
|
52141
52207
|
for (const file2 of files) {
|
|
52142
|
-
const name = nameFromUnitFile(file2, backend);
|
|
52208
|
+
const name = nameFromUnitFile(kind, file2, backend);
|
|
52143
52209
|
if (name) out.push({ name, unitPath: (0, import_node_path5.join)(unitDir, file2) });
|
|
52144
52210
|
}
|
|
52145
52211
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
52146
52212
|
}
|
|
52147
|
-
function
|
|
52213
|
+
function readUnitRoot(kind, name, backend, unitDir = defaultUnitDir(backend)) {
|
|
52148
52214
|
let text;
|
|
52149
52215
|
try {
|
|
52150
|
-
text = (0, import_node_fs5.readFileSync)(
|
|
52216
|
+
text = (0, import_node_fs5.readFileSync)(unitPath(kind, name, backend, unitDir), "utf8");
|
|
52151
52217
|
} catch {
|
|
52152
52218
|
return null;
|
|
52153
52219
|
}
|
|
@@ -52156,75 +52222,55 @@ function readHubUnitRoot(name, backend, unitDir = defaultUnitDir(backend)) {
|
|
|
52156
52222
|
if (!raw) return null;
|
|
52157
52223
|
return backend === "launchd" ? raw.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/&/g, "&") : raw;
|
|
52158
52224
|
}
|
|
52159
|
-
function
|
|
52160
|
-
|
|
52161
|
-
|
|
52225
|
+
function serviceLogNames(kind) {
|
|
52226
|
+
return { out: KINDS[kind].outLog, err: KINDS[kind].errLog };
|
|
52227
|
+
}
|
|
52228
|
+
function systemdUnitName(kind, name) {
|
|
52229
|
+
assertValidServiceName(kind, name);
|
|
52230
|
+
return `${KINDS[kind].systemdPrefix}${name}.service`;
|
|
52162
52231
|
}
|
|
52163
|
-
function launchdLabel(name) {
|
|
52164
|
-
|
|
52165
|
-
return `${
|
|
52232
|
+
function launchdLabel(kind, name) {
|
|
52233
|
+
assertValidServiceName(kind, name);
|
|
52234
|
+
return `${KINDS[kind].labelPrefix}${name}`;
|
|
52166
52235
|
}
|
|
52167
52236
|
|
|
52168
|
-
// src/
|
|
52169
|
-
var
|
|
52170
|
-
var
|
|
52237
|
+
// src/service.ts
|
|
52238
|
+
var import_node_child_process2 = require("node:child_process");
|
|
52239
|
+
var import_node_fs6 = require("node:fs");
|
|
52240
|
+
var import_node_module = require("node:module");
|
|
52241
|
+
var import_node_path6 = require("node:path");
|
|
52242
|
+
var CLI_VERSION = true ? "0.36.0-beta.11" : "";
|
|
52171
52243
|
function fail(message) {
|
|
52172
52244
|
console.error(message);
|
|
52173
52245
|
process.exit(1);
|
|
52174
52246
|
}
|
|
52175
|
-
function outOfBlockPorts() {
|
|
52176
|
-
if (!hasLocalDatabase()) return [];
|
|
52177
|
-
try {
|
|
52178
|
-
const port = getServerPort();
|
|
52179
|
-
return port ? [port] : [];
|
|
52180
|
-
} catch {
|
|
52181
|
-
return [];
|
|
52182
|
-
}
|
|
52183
|
-
}
|
|
52184
|
-
function discoverAllHubs() {
|
|
52185
|
-
return discoverHubs({ extraPorts: outOfBlockPorts() });
|
|
52186
|
-
}
|
|
52187
|
-
function findAnyHub(nameOrPort) {
|
|
52188
|
-
return findHub(nameOrPort, { extraPorts: outOfBlockPorts() });
|
|
52189
|
-
}
|
|
52190
|
-
function formatUptime(ms) {
|
|
52191
|
-
const s = Math.floor(ms / 1e3);
|
|
52192
|
-
if (s < 60) return `${s}s`;
|
|
52193
|
-
const m = Math.floor(s / 60);
|
|
52194
|
-
if (m < 60) return `${m}m`;
|
|
52195
|
-
const h = Math.floor(m / 60);
|
|
52196
|
-
if (h < 24) return `${h}h ${m % 60}m`;
|
|
52197
|
-
return `${Math.floor(h / 24)}d ${h % 24}h`;
|
|
52198
|
-
}
|
|
52199
52247
|
function shortenPath(path10) {
|
|
52200
52248
|
const home = process.env.HOME ?? "";
|
|
52201
52249
|
return home && path10.startsWith(home) ? `~${path10.slice(home.length)}` : path10;
|
|
52202
52250
|
}
|
|
52203
|
-
|
|
52204
|
-
|
|
52205
|
-
if (!hub) {
|
|
52206
|
-
const running = await discoverAllHubs();
|
|
52207
|
-
const hint = running.length > 0 ? `Running hubs: ${running.map((h) => `${h.name} (${h.port})`).join(", ")}` : "No hubs are running on this machine.";
|
|
52208
|
-
fail(`No hub named or listening on "${nameOrPort}".
|
|
52209
|
-
${hint}`);
|
|
52210
|
-
}
|
|
52211
|
-
return hub;
|
|
52251
|
+
function resolveBackend() {
|
|
52252
|
+
return process.env.SLZ_FORCE_NO_SERVICE === "1" ? "none" : detectBackend();
|
|
52212
52253
|
}
|
|
52213
|
-
|
|
52214
|
-
|
|
52215
|
-
|
|
52216
|
-
|
|
52217
|
-
|
|
52254
|
+
var PACKAGES = {
|
|
52255
|
+
hub: { pkg: "@slayzone/hub", bin: "slayzone-hub", devDir: "hub", envOverride: "SLZ_HUB_BIN" },
|
|
52256
|
+
runner: {
|
|
52257
|
+
pkg: "@slayzone/runner",
|
|
52258
|
+
bin: "slayzone-runner",
|
|
52259
|
+
devDir: "runner",
|
|
52260
|
+
envOverride: "SLZ_RUNNER_BIN"
|
|
52218
52261
|
}
|
|
52262
|
+
};
|
|
52263
|
+
function servicePackage(kind) {
|
|
52264
|
+
return PACKAGES[kind].pkg;
|
|
52219
52265
|
}
|
|
52220
|
-
function
|
|
52266
|
+
function readServicePkg(kind, pkgJsonPath) {
|
|
52221
52267
|
let pkg;
|
|
52222
52268
|
try {
|
|
52223
52269
|
pkg = JSON.parse((0, import_node_fs6.readFileSync)(pkgJsonPath, "utf8"));
|
|
52224
52270
|
} catch {
|
|
52225
52271
|
return null;
|
|
52226
52272
|
}
|
|
52227
|
-
const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.[
|
|
52273
|
+
const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.[PACKAGES[kind].bin];
|
|
52228
52274
|
if (!binRel) return null;
|
|
52229
52275
|
const binPath = (0, import_node_path6.join)((0, import_node_path6.dirname)(pkgJsonPath), binRel);
|
|
52230
52276
|
if (!(0, import_node_fs6.existsSync)(binPath)) return null;
|
|
@@ -52256,8 +52302,9 @@ function interpreterFor(binPath, version2) {
|
|
|
52256
52302
|
}
|
|
52257
52303
|
return { command: process.execPath, args: [binPath], version: version2 };
|
|
52258
52304
|
}
|
|
52259
|
-
function
|
|
52260
|
-
const
|
|
52305
|
+
function resolveServiceBin(kind) {
|
|
52306
|
+
const { pkg, devDir, envOverride } = PACKAGES[kind];
|
|
52307
|
+
const override = process.env[envOverride];
|
|
52261
52308
|
if (override) return { command: process.execPath, args: [override], version: "override" };
|
|
52262
52309
|
const candidates = [
|
|
52263
52310
|
// (1) Anchored on argv[1] (the slay script) — `import.meta` does not exist in
|
|
@@ -52265,30 +52312,30 @@ function resolveHubBin() {
|
|
|
52265
52312
|
() => {
|
|
52266
52313
|
try {
|
|
52267
52314
|
return (0, import_node_module.createRequire)(process.argv[1] ?? (0, import_node_path6.join)(process.cwd(), "x")).resolve(
|
|
52268
|
-
`${
|
|
52315
|
+
`${pkg}/package.json`
|
|
52269
52316
|
);
|
|
52270
52317
|
} catch {
|
|
52271
52318
|
return null;
|
|
52272
52319
|
}
|
|
52273
52320
|
},
|
|
52274
|
-
// (2) Dev tree: <cli>/dist/slay.js →
|
|
52321
|
+
// (2) Dev tree: <cli>/dist/slay.js → ../../<kind>/package.json
|
|
52275
52322
|
() => {
|
|
52276
52323
|
const self2 = process.argv[1];
|
|
52277
52324
|
if (!self2) return null;
|
|
52278
|
-
const sibling = (0, import_node_path6.join)((0, import_node_path6.dirname)(self2), "..", "..",
|
|
52325
|
+
const sibling = (0, import_node_path6.join)((0, import_node_path6.dirname)(self2), "..", "..", devDir, "package.json");
|
|
52279
52326
|
return (0, import_node_fs6.existsSync)(sibling) ? sibling : null;
|
|
52280
52327
|
}
|
|
52281
52328
|
];
|
|
52282
52329
|
for (const find of candidates) {
|
|
52283
52330
|
const path10 = find();
|
|
52284
52331
|
if (!path10) continue;
|
|
52285
|
-
const found2 =
|
|
52332
|
+
const found2 = readServicePkg(kind, path10);
|
|
52286
52333
|
if (found2) return interpreterFor(found2.binPath, found2.version);
|
|
52287
52334
|
}
|
|
52288
|
-
const prefix = (0, import_node_path6.join)(getDataDir(),
|
|
52289
|
-
const installed = (0, import_node_path6.join)(prefix, "node_modules",
|
|
52290
|
-
const wanted = CLI_VERSION ? `${
|
|
52291
|
-
if (!(0, import_node_fs6.existsSync)(installed) || CLI_VERSION &&
|
|
52335
|
+
const prefix = (0, import_node_path6.join)(getDataDir(), `${kind}-runtime`);
|
|
52336
|
+
const installed = (0, import_node_path6.join)(prefix, "node_modules", pkg, "package.json");
|
|
52337
|
+
const wanted = CLI_VERSION ? `${pkg}@${CLI_VERSION}` : `${pkg}@beta`;
|
|
52338
|
+
if (!(0, import_node_fs6.existsSync)(installed) || CLI_VERSION && readServicePkg(kind, installed)?.version !== CLI_VERSION) {
|
|
52292
52339
|
console.log(`Installing ${wanted}\u2026`);
|
|
52293
52340
|
(0, import_node_fs6.mkdirSync)(prefix, { recursive: true });
|
|
52294
52341
|
try {
|
|
@@ -52304,20 +52351,232 @@ function resolveHubBin() {
|
|
|
52304
52351
|
);
|
|
52305
52352
|
}
|
|
52306
52353
|
}
|
|
52307
|
-
const found =
|
|
52354
|
+
const found = readServicePkg(kind, installed);
|
|
52308
52355
|
if (!found) {
|
|
52309
52356
|
fail(
|
|
52310
|
-
`Installed ${
|
|
52311
|
-
npm install -g ${
|
|
52357
|
+
`Installed ${pkg} but found no usable bin at ${installed}. Install it globally and retry:
|
|
52358
|
+
npm install -g ${pkg}`
|
|
52312
52359
|
);
|
|
52313
52360
|
}
|
|
52314
52361
|
return interpreterFor(found.binPath, found.version);
|
|
52315
52362
|
}
|
|
52316
|
-
function ensureLogDir(root) {
|
|
52317
|
-
const dir = (
|
|
52363
|
+
function ensureLogDir(kind, root) {
|
|
52364
|
+
const dir = logDirFor(kind, root);
|
|
52318
52365
|
(0, import_node_fs6.mkdirSync)(dir, { recursive: true });
|
|
52319
52366
|
return dir;
|
|
52320
52367
|
}
|
|
52368
|
+
function logDirFor(kind, root) {
|
|
52369
|
+
return kind === "hub" ? (0, import_node_path6.join)(root, "storage", "logs") : (0, import_node_path6.join)(root, "logs");
|
|
52370
|
+
}
|
|
52371
|
+
function serviceLogPaths(kind, root) {
|
|
52372
|
+
const dir = logDirFor(kind, root);
|
|
52373
|
+
const names = serviceLogNames(kind);
|
|
52374
|
+
return { out: (0, import_node_path6.join)(dir, names.out), err: (0, import_node_path6.join)(dir, names.err) };
|
|
52375
|
+
}
|
|
52376
|
+
function failWithLog(kind, name, logPath, what) {
|
|
52377
|
+
let detail = "";
|
|
52378
|
+
try {
|
|
52379
|
+
detail = (0, import_node_fs6.readFileSync)(logPath, "utf8").trim().split("\n").slice(-12).join("\n");
|
|
52380
|
+
} catch {
|
|
52381
|
+
}
|
|
52382
|
+
const label = kind === "hub" ? "Hub" : "Runner";
|
|
52383
|
+
fail(
|
|
52384
|
+
`${label} "${name}" ${what}.
|
|
52385
|
+
` + (detail ? `
|
|
52386
|
+
Last output (${shortenPath(logPath)}):
|
|
52387
|
+
${detail}
|
|
52388
|
+
` : `
|
|
52389
|
+
No output was captured in ${shortenPath(logPath)}.
|
|
52390
|
+
`)
|
|
52391
|
+
);
|
|
52392
|
+
}
|
|
52393
|
+
function readServiceLogTail(kind, root, lines) {
|
|
52394
|
+
const dir = logDirFor(kind, root);
|
|
52395
|
+
let files;
|
|
52396
|
+
try {
|
|
52397
|
+
files = (0, import_node_fs6.readdirSync)(dir).filter((f) => f.endsWith(".log"));
|
|
52398
|
+
} catch {
|
|
52399
|
+
return "";
|
|
52400
|
+
}
|
|
52401
|
+
const chunks = [];
|
|
52402
|
+
for (const f of files) {
|
|
52403
|
+
try {
|
|
52404
|
+
chunks.push((0, import_node_fs6.readFileSync)((0, import_node_path6.join)(dir, f), "utf8"));
|
|
52405
|
+
} catch {
|
|
52406
|
+
}
|
|
52407
|
+
}
|
|
52408
|
+
return chunks.join("\n").trim().split("\n").slice(-lines).join("\n");
|
|
52409
|
+
}
|
|
52410
|
+
function followServiceLog(args) {
|
|
52411
|
+
const { kind, name, root, lines, follow, systemdUnit } = args;
|
|
52412
|
+
if (systemdUnit) {
|
|
52413
|
+
const jargs = ["--user", "-u", systemdUnitName(kind, name), "-n", String(lines)];
|
|
52414
|
+
if (follow) jargs.push("-f");
|
|
52415
|
+
const child2 = (0, import_node_child_process2.spawn)("journalctl", jargs, { stdio: "inherit" });
|
|
52416
|
+
child2.on("exit", (code) => process.exit(code ?? 0));
|
|
52417
|
+
return;
|
|
52418
|
+
}
|
|
52419
|
+
const dir = logDirFor(kind, root);
|
|
52420
|
+
if (!(0, import_node_fs6.existsSync)(dir)) fail(`No log directory at ${shortenPath(dir)}.`);
|
|
52421
|
+
const candidates = (0, import_node_fs6.readdirSync)(dir).filter((f) => f.endsWith(".log")).map((f) => ({ f, path: (0, import_node_path6.join)(dir, f), mtime: (0, import_node_fs6.statSync)((0, import_node_path6.join)(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
52422
|
+
if (candidates.length === 0) fail(`No log files in ${shortenPath(dir)}.`);
|
|
52423
|
+
const target = candidates[0].path;
|
|
52424
|
+
const targs = follow ? ["-f", "-n", String(lines), target] : ["-n", String(lines), target];
|
|
52425
|
+
const child = (0, import_node_child_process2.spawn)("tail", targs, { stdio: "inherit" });
|
|
52426
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
52427
|
+
}
|
|
52428
|
+
var SupervisorError = class extends Error {
|
|
52429
|
+
constructor(command, output) {
|
|
52430
|
+
super(`${command} failed`);
|
|
52431
|
+
this.command = command;
|
|
52432
|
+
this.output = output;
|
|
52433
|
+
}
|
|
52434
|
+
};
|
|
52435
|
+
function run(file2, args) {
|
|
52436
|
+
try {
|
|
52437
|
+
(0, import_node_child_process2.execFileSync)(file2, args, { stdio: ["ignore", "inherit", "pipe"] });
|
|
52438
|
+
} catch (e) {
|
|
52439
|
+
const stderr = e.stderr;
|
|
52440
|
+
throw new SupervisorError(
|
|
52441
|
+
`${file2} ${args.join(" ")}`,
|
|
52442
|
+
(typeof stderr === "string" ? stderr : stderr?.toString() ?? "").trim()
|
|
52443
|
+
);
|
|
52444
|
+
}
|
|
52445
|
+
}
|
|
52446
|
+
function launchdDomain() {
|
|
52447
|
+
return `gui/${process.getuid?.() ?? ""}`;
|
|
52448
|
+
}
|
|
52449
|
+
function supervisorStart(kind, backend, name, unitFilePath) {
|
|
52450
|
+
if (backend === "launchd") {
|
|
52451
|
+
const label = launchdLabel(kind, name);
|
|
52452
|
+
const domain2 = launchdDomain();
|
|
52453
|
+
try {
|
|
52454
|
+
(0, import_node_child_process2.execFileSync)("launchctl", ["bootout", `${domain2}/${label}`], { stdio: "ignore" });
|
|
52455
|
+
} catch {
|
|
52456
|
+
}
|
|
52457
|
+
run("launchctl", ["bootstrap", domain2, unitFilePath]);
|
|
52458
|
+
return;
|
|
52459
|
+
}
|
|
52460
|
+
const unit = systemdUnitName(kind, name);
|
|
52461
|
+
run("systemctl", ["--user", "daemon-reload"]);
|
|
52462
|
+
run("systemctl", ["--user", "enable", "--now", unit]);
|
|
52463
|
+
try {
|
|
52464
|
+
(0, import_node_child_process2.execFileSync)("loginctl", ["enable-linger", process.env.USER ?? ""], { stdio: "ignore" });
|
|
52465
|
+
} catch {
|
|
52466
|
+
console.log(
|
|
52467
|
+
`Note: could not enable systemd lingering \u2014 the ${kind} will stop when you log out.`
|
|
52468
|
+
);
|
|
52469
|
+
console.log(` Fix with: sudo loginctl enable-linger ${process.env.USER ?? "<user>"}`);
|
|
52470
|
+
}
|
|
52471
|
+
}
|
|
52472
|
+
function supervisorStop(kind, backend, name) {
|
|
52473
|
+
if (backend === "launchd") {
|
|
52474
|
+
try {
|
|
52475
|
+
(0, import_node_child_process2.execFileSync)("launchctl", ["bootout", `${launchdDomain()}/${launchdLabel(kind, name)}`], {
|
|
52476
|
+
stdio: "ignore"
|
|
52477
|
+
});
|
|
52478
|
+
} catch {
|
|
52479
|
+
}
|
|
52480
|
+
return;
|
|
52481
|
+
}
|
|
52482
|
+
try {
|
|
52483
|
+
(0, import_node_child_process2.execFileSync)("systemctl", ["--user", "disable", "--now", systemdUnitName(kind, name)], {
|
|
52484
|
+
stdio: "ignore"
|
|
52485
|
+
});
|
|
52486
|
+
} catch {
|
|
52487
|
+
}
|
|
52488
|
+
}
|
|
52489
|
+
function supervisorStopQuiet(kind, backend, name) {
|
|
52490
|
+
try {
|
|
52491
|
+
supervisorStop(kind, backend, name);
|
|
52492
|
+
} catch {
|
|
52493
|
+
}
|
|
52494
|
+
}
|
|
52495
|
+
function supervisorStatus(kind, backend, name) {
|
|
52496
|
+
const none = { running: false, pid: null, lastExit: null };
|
|
52497
|
+
if (backend === "none") return none;
|
|
52498
|
+
if (backend === "launchd") {
|
|
52499
|
+
let out2;
|
|
52500
|
+
try {
|
|
52501
|
+
out2 = (0, import_node_child_process2.execFileSync)("launchctl", ["list", launchdLabel(kind, name)], {
|
|
52502
|
+
encoding: "utf8",
|
|
52503
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
52504
|
+
});
|
|
52505
|
+
} catch {
|
|
52506
|
+
return none;
|
|
52507
|
+
}
|
|
52508
|
+
const pid = /"PID"\s*=\s*(\d+)/.exec(out2)?.[1];
|
|
52509
|
+
const exit2 = /"LastExitStatus"\s*=\s*(-?\d+)/.exec(out2)?.[1];
|
|
52510
|
+
return {
|
|
52511
|
+
running: pid !== void 0,
|
|
52512
|
+
pid: pid === void 0 ? null : Number(pid),
|
|
52513
|
+
lastExit: exit2 === void 0 ? null : Number(exit2)
|
|
52514
|
+
};
|
|
52515
|
+
}
|
|
52516
|
+
let out;
|
|
52517
|
+
try {
|
|
52518
|
+
out = (0, import_node_child_process2.execFileSync)(
|
|
52519
|
+
"systemctl",
|
|
52520
|
+
["--user", "show", systemdUnitName(kind, name), "-p", "ActiveState", "-p", "MainPID", "-p", "ExecMainStatus"],
|
|
52521
|
+
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
52522
|
+
);
|
|
52523
|
+
} catch {
|
|
52524
|
+
return none;
|
|
52525
|
+
}
|
|
52526
|
+
const field = (key) => new RegExp(`^${key}=(.*)$`, "m").exec(out)?.[1]?.trim();
|
|
52527
|
+
const mainPid = Number(field("MainPID") ?? "0");
|
|
52528
|
+
const exit = field("ExecMainStatus");
|
|
52529
|
+
return {
|
|
52530
|
+
running: field("ActiveState") === "active" && mainPid > 0,
|
|
52531
|
+
// MainPID is 0 for an inactive unit — report absence, not pid 0.
|
|
52532
|
+
pid: mainPid > 0 ? mainPid : null,
|
|
52533
|
+
lastExit: exit === void 0 || exit === "" ? null : Number(exit)
|
|
52534
|
+
};
|
|
52535
|
+
}
|
|
52536
|
+
|
|
52537
|
+
// src/commands/hub.ts
|
|
52538
|
+
var HUB_PACKAGE = servicePackage("hub");
|
|
52539
|
+
function outOfBlockPorts() {
|
|
52540
|
+
if (!hasLocalDatabase()) return [];
|
|
52541
|
+
try {
|
|
52542
|
+
const port = getServerPort();
|
|
52543
|
+
return port ? [port] : [];
|
|
52544
|
+
} catch {
|
|
52545
|
+
return [];
|
|
52546
|
+
}
|
|
52547
|
+
}
|
|
52548
|
+
function discoverAllHubs() {
|
|
52549
|
+
return discoverHubs({ extraPorts: outOfBlockPorts() });
|
|
52550
|
+
}
|
|
52551
|
+
function findAnyHub(nameOrPort) {
|
|
52552
|
+
return findHub(nameOrPort, { extraPorts: outOfBlockPorts() });
|
|
52553
|
+
}
|
|
52554
|
+
function formatUptime(ms) {
|
|
52555
|
+
const s = Math.floor(ms / 1e3);
|
|
52556
|
+
if (s < 60) return `${s}s`;
|
|
52557
|
+
const m = Math.floor(s / 60);
|
|
52558
|
+
if (m < 60) return `${m}m`;
|
|
52559
|
+
const h = Math.floor(m / 60);
|
|
52560
|
+
if (h < 24) return `${h}h ${m % 60}m`;
|
|
52561
|
+
return `${Math.floor(h / 24)}d ${h % 24}h`;
|
|
52562
|
+
}
|
|
52563
|
+
async function requireHub(nameOrPort) {
|
|
52564
|
+
const hub = await findAnyHub(nameOrPort);
|
|
52565
|
+
if (!hub) {
|
|
52566
|
+
const running = await discoverAllHubs();
|
|
52567
|
+
const hint = running.length > 0 ? `Running hubs: ${running.map((h) => `${h.name} (${h.port})`).join(", ")}` : "No hubs are running on this machine.";
|
|
52568
|
+
fail(`No hub named or listening on "${nameOrPort}".
|
|
52569
|
+
${hint}`);
|
|
52570
|
+
}
|
|
52571
|
+
return hub;
|
|
52572
|
+
}
|
|
52573
|
+
function refuseSupervised(hub, verb) {
|
|
52574
|
+
if (hub.supervised) {
|
|
52575
|
+
fail(
|
|
52576
|
+
`Refusing to ${verb} "${hub.name}" \u2014 it is the hub inside the SlayZone desktop app, which manages its own lifecycle. Quit the app instead.`
|
|
52577
|
+
);
|
|
52578
|
+
}
|
|
52579
|
+
}
|
|
52321
52580
|
async function waitForHub(predicate, timeoutMs) {
|
|
52322
52581
|
const deadline = Date.now() + timeoutMs;
|
|
52323
52582
|
for (; ; ) {
|
|
@@ -52335,17 +52594,15 @@ async function waitForHubGone(port, timeoutMs) {
|
|
|
52335
52594
|
await new Promise((r) => setTimeout(r, 200));
|
|
52336
52595
|
}
|
|
52337
52596
|
}
|
|
52338
|
-
function resolveBackend() {
|
|
52339
|
-
return process.env.SLZ_FORCE_NO_SERVICE === "1" ? "none" : detectBackend();
|
|
52340
|
-
}
|
|
52341
52597
|
async function launchHub(args) {
|
|
52342
52598
|
const { name, root, port, backend, creating } = args;
|
|
52343
|
-
const bin =
|
|
52344
|
-
const logDir = ensureLogDir(root);
|
|
52599
|
+
const bin = resolveServiceBin("hub");
|
|
52600
|
+
const logDir = ensureLogDir("hub", root);
|
|
52601
|
+
const logs = serviceLogPaths("hub", root);
|
|
52345
52602
|
const findIt = () => port === void 0 ? findAnyHub(name) : findHub(String(port));
|
|
52346
52603
|
if (backend === "none") {
|
|
52347
|
-
const out = (0,
|
|
52348
|
-
const child = (0,
|
|
52604
|
+
const out = (0, import_node_fs7.openSync)(logs.out, "a");
|
|
52605
|
+
const child = (0, import_node_child_process3.spawn)(bin.command, bin.args, {
|
|
52349
52606
|
cwd: root,
|
|
52350
52607
|
detached: true,
|
|
52351
52608
|
stdio: ["ignore", out, out],
|
|
@@ -52361,7 +52618,7 @@ async function launchHub(args) {
|
|
|
52361
52618
|
});
|
|
52362
52619
|
child.unref();
|
|
52363
52620
|
const started2 = await waitForHub(findIt, 2e4);
|
|
52364
|
-
if (!started2) failWithLog(
|
|
52621
|
+
if (!started2) failWithLog("hub", name, logs.out, "did not come up within 20s");
|
|
52365
52622
|
console.log(`Hub "${started2.name}" started on port ${started2.port} (pid ${started2.pid}).`);
|
|
52366
52623
|
console.log(` Root: ${shortenPath(root)}`);
|
|
52367
52624
|
console.log(` Logs: ${shortenPath(logDir)}`);
|
|
@@ -52370,8 +52627,9 @@ async function launchHub(args) {
|
|
|
52370
52627
|
);
|
|
52371
52628
|
return;
|
|
52372
52629
|
}
|
|
52373
|
-
const
|
|
52630
|
+
const writtenUnitPath = writeUnit(
|
|
52374
52631
|
{
|
|
52632
|
+
kind: "hub",
|
|
52375
52633
|
name,
|
|
52376
52634
|
root,
|
|
52377
52635
|
command: bin.command,
|
|
@@ -52382,12 +52640,12 @@ async function launchHub(args) {
|
|
|
52382
52640
|
},
|
|
52383
52641
|
backend
|
|
52384
52642
|
);
|
|
52385
|
-
if (creating) console.log(`Registered ${
|
|
52643
|
+
if (creating) console.log(`Registered ${writtenUnitPath}`);
|
|
52386
52644
|
console.log(`Starting hub "${name}" (${HUB_PACKAGE}@${bin.version})\u2026`);
|
|
52387
52645
|
try {
|
|
52388
|
-
supervisorStart(backend, name,
|
|
52646
|
+
supervisorStart("hub", backend, name, writtenUnitPath);
|
|
52389
52647
|
} catch (e) {
|
|
52390
|
-
if (creating)
|
|
52648
|
+
if (creating) removeUnit("hub", name, backend);
|
|
52391
52649
|
if (!(e instanceof SupervisorError)) throw e;
|
|
52392
52650
|
const noBus = /Failed to connect to bus|No medium found/i.test(e.output);
|
|
52393
52651
|
fail(
|
|
@@ -52406,28 +52664,29 @@ then log out and back in, and retry. If this account is not meant to have one (a
|
|
|
52406
52664
|
const started = await waitForHub(findIt, 2e4);
|
|
52407
52665
|
if (!started) {
|
|
52408
52666
|
if (creating) {
|
|
52409
|
-
supervisorStop(backend, name);
|
|
52410
|
-
|
|
52667
|
+
supervisorStop("hub", backend, name);
|
|
52668
|
+
removeUnit("hub", name, backend);
|
|
52411
52669
|
} else {
|
|
52412
|
-
supervisorStop(backend, name);
|
|
52670
|
+
supervisorStop("hub", backend, name);
|
|
52413
52671
|
}
|
|
52414
52672
|
failWithLog(
|
|
52673
|
+
"hub",
|
|
52415
52674
|
name,
|
|
52416
|
-
|
|
52675
|
+
logs.err,
|
|
52417
52676
|
creating ? "failed to start, so it was unregistered again" : "failed to start (its registration was left in place)"
|
|
52418
52677
|
);
|
|
52419
52678
|
}
|
|
52420
52679
|
console.log(`Hub "${started.name}" running on port ${started.port} (pid ${started.pid}).`);
|
|
52421
52680
|
console.log(` Root: ${shortenPath(root)}`);
|
|
52422
52681
|
console.log(` Logs: ${shortenPath(logDir)}`);
|
|
52423
|
-
console.log(` Unit: ${
|
|
52682
|
+
console.log(` Unit: ${writtenUnitPath}`);
|
|
52424
52683
|
console.log(` Hub: ${HUB_PACKAGE}@${bin.version}`);
|
|
52425
52684
|
console.log("Restarts automatically if it crashes, and starts again when you log in.");
|
|
52426
52685
|
}
|
|
52427
52686
|
async function haltHub(hub, backend, opts) {
|
|
52428
|
-
if (backend !== "none" && (0,
|
|
52429
|
-
supervisorStop(backend, hub.name);
|
|
52430
|
-
if (opts.unregister)
|
|
52687
|
+
if (backend !== "none" && (0, import_node_fs7.existsSync)(unitPath("hub", hub.name, backend))) {
|
|
52688
|
+
supervisorStop("hub", backend, hub.name);
|
|
52689
|
+
if (opts.unregister) removeUnit("hub", hub.name, backend);
|
|
52431
52690
|
} else {
|
|
52432
52691
|
try {
|
|
52433
52692
|
process.kill(hub.pid, "SIGTERM");
|
|
@@ -52442,83 +52701,6 @@ async function haltHub(hub, backend, opts) {
|
|
|
52442
52701
|
);
|
|
52443
52702
|
}
|
|
52444
52703
|
}
|
|
52445
|
-
var SupervisorError = class extends Error {
|
|
52446
|
-
constructor(command, output) {
|
|
52447
|
-
super(`${command} failed`);
|
|
52448
|
-
this.command = command;
|
|
52449
|
-
this.output = output;
|
|
52450
|
-
}
|
|
52451
|
-
};
|
|
52452
|
-
function run(file2, args) {
|
|
52453
|
-
try {
|
|
52454
|
-
(0, import_node_child_process2.execFileSync)(file2, args, { stdio: ["ignore", "inherit", "pipe"] });
|
|
52455
|
-
} catch (e) {
|
|
52456
|
-
const stderr = e.stderr;
|
|
52457
|
-
throw new SupervisorError(
|
|
52458
|
-
`${file2} ${args.join(" ")}`,
|
|
52459
|
-
(typeof stderr === "string" ? stderr : stderr?.toString() ?? "").trim()
|
|
52460
|
-
);
|
|
52461
|
-
}
|
|
52462
|
-
}
|
|
52463
|
-
function failWithLog(name, logPath, what) {
|
|
52464
|
-
let detail = "";
|
|
52465
|
-
try {
|
|
52466
|
-
detail = (0, import_node_fs6.readFileSync)(logPath, "utf8").trim().split("\n").slice(-12).join("\n");
|
|
52467
|
-
} catch {
|
|
52468
|
-
}
|
|
52469
|
-
fail(
|
|
52470
|
-
`Hub "${name}" ${what}.
|
|
52471
|
-
` + (detail ? `
|
|
52472
|
-
Last output (${shortenPath(logPath)}):
|
|
52473
|
-
${detail}
|
|
52474
|
-
` : `
|
|
52475
|
-
No output was captured in ${shortenPath(logPath)}.
|
|
52476
|
-
`)
|
|
52477
|
-
);
|
|
52478
|
-
}
|
|
52479
|
-
function supervisorStart(backend, name, unitPath) {
|
|
52480
|
-
if (backend === "launchd") {
|
|
52481
|
-
const label = launchdLabel(name);
|
|
52482
|
-
const domain2 = `gui/${process.getuid?.() ?? ""}`;
|
|
52483
|
-
try {
|
|
52484
|
-
(0, import_node_child_process2.execFileSync)("launchctl", ["bootout", `${domain2}/${label}`], { stdio: "ignore" });
|
|
52485
|
-
} catch {
|
|
52486
|
-
}
|
|
52487
|
-
run("launchctl", ["bootstrap", domain2, unitPath]);
|
|
52488
|
-
return;
|
|
52489
|
-
}
|
|
52490
|
-
const unit = systemdUnitName(name);
|
|
52491
|
-
run("systemctl", ["--user", "daemon-reload"]);
|
|
52492
|
-
run("systemctl", ["--user", "enable", "--now", unit]);
|
|
52493
|
-
try {
|
|
52494
|
-
(0, import_node_child_process2.execFileSync)("loginctl", ["enable-linger", process.env.USER ?? ""], { stdio: "ignore" });
|
|
52495
|
-
} catch {
|
|
52496
|
-
console.log("Note: could not enable systemd lingering \u2014 the hub will stop when you log out.");
|
|
52497
|
-
console.log(` Fix with: sudo loginctl enable-linger ${process.env.USER ?? "<user>"}`);
|
|
52498
|
-
}
|
|
52499
|
-
}
|
|
52500
|
-
function supervisorStopQuiet(backend, name) {
|
|
52501
|
-
try {
|
|
52502
|
-
supervisorStop(backend, name);
|
|
52503
|
-
} catch {
|
|
52504
|
-
}
|
|
52505
|
-
}
|
|
52506
|
-
function supervisorStop(backend, name) {
|
|
52507
|
-
if (backend === "launchd") {
|
|
52508
|
-
const domain2 = `gui/${process.getuid?.() ?? ""}`;
|
|
52509
|
-
try {
|
|
52510
|
-
(0, import_node_child_process2.execFileSync)("launchctl", ["bootout", `${domain2}/${launchdLabel(name)}`], { stdio: "ignore" });
|
|
52511
|
-
} catch {
|
|
52512
|
-
}
|
|
52513
|
-
return;
|
|
52514
|
-
}
|
|
52515
|
-
try {
|
|
52516
|
-
(0, import_node_child_process2.execFileSync)("systemctl", ["--user", "disable", "--now", systemdUnitName(name)], {
|
|
52517
|
-
stdio: "ignore"
|
|
52518
|
-
});
|
|
52519
|
-
} catch {
|
|
52520
|
-
}
|
|
52521
|
-
}
|
|
52522
52704
|
function hubCommand() {
|
|
52523
52705
|
const cmd = new Command("hub").description("Run, list and target SlayZone hubs").showSuggestionAfterError(true).showHelpAfterError(true);
|
|
52524
52706
|
cmd.command("ls").description("List the hubs running on this machine").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -52550,14 +52732,14 @@ function hubCommand() {
|
|
|
52550
52732
|
}
|
|
52551
52733
|
});
|
|
52552
52734
|
cmd.command("create <name>").description("Create a hub here and keep it running (crash-restart + start at login)").option("--root <dir>", "Hub root \u2014 its config, storage and logs (default: cwd)").option("--port <port>", "Bind a specific port (default: first free hub port)").action(async (name, opts) => {
|
|
52553
|
-
const root = (0,
|
|
52735
|
+
const root = (0, import_node_path7.resolve)(opts.root ?? process.cwd());
|
|
52554
52736
|
const port = opts.port === void 0 ? void 0 : Number(opts.port);
|
|
52555
52737
|
if (port !== void 0 && (!Number.isInteger(port) || port < 1 || port > 65535)) {
|
|
52556
52738
|
fail(`Invalid --port ${opts.port} \u2014 expected an integer 1-65535.`);
|
|
52557
52739
|
}
|
|
52558
52740
|
const backend = resolveBackend();
|
|
52559
|
-
if (backend !== "none" && (0,
|
|
52560
|
-
const existingRoot =
|
|
52741
|
+
if (backend !== "none" && (0, import_node_fs7.existsSync)(unitPath("hub", name, backend))) {
|
|
52742
|
+
const existingRoot = readUnitRoot("hub", name, backend);
|
|
52561
52743
|
fail(
|
|
52562
52744
|
`A hub named "${name}" already exists${existingRoot ? ` (root ${shortenPath(existingRoot)})` : ""}.
|
|
52563
52745
|
Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name}\`.`
|
|
@@ -52592,12 +52774,12 @@ Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name
|
|
|
52592
52774
|
`This platform has no user service manager, so hubs are not registered and \`start\` has nothing to act on. Use \`slay hub create ${name}\`.`
|
|
52593
52775
|
);
|
|
52594
52776
|
}
|
|
52595
|
-
if (!(0,
|
|
52777
|
+
if (!(0, import_node_fs7.existsSync)(unitPath("hub", name, backend))) {
|
|
52596
52778
|
fail(
|
|
52597
52779
|
`No hub named "${name}". Create it with \`slay hub create ${name}\`, or list what exists with \`slay hub registered\`.`
|
|
52598
52780
|
);
|
|
52599
52781
|
}
|
|
52600
|
-
const root =
|
|
52782
|
+
const root = readUnitRoot("hub", name, backend);
|
|
52601
52783
|
if (!root) {
|
|
52602
52784
|
fail(
|
|
52603
52785
|
`The unit for "${name}" does not record a root \u2014 it may be hand-edited. Remove it with \`slay hub rm ${name}\` and create it again.`
|
|
@@ -52609,7 +52791,7 @@ Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name
|
|
|
52609
52791
|
const hub = await requireHub(nameOrPort);
|
|
52610
52792
|
refuseSupervised(hub, "stop");
|
|
52611
52793
|
const backend = resolveBackend();
|
|
52612
|
-
const registered = backend !== "none" && (0,
|
|
52794
|
+
const registered = backend !== "none" && (0, import_node_fs7.existsSync)(unitPath("hub", hub.name, backend));
|
|
52613
52795
|
await haltHub(hub, backend, { unregister: false });
|
|
52614
52796
|
console.log(
|
|
52615
52797
|
registered ? `Stopped "${hub.name}". Start it again with \`slay hub start ${hub.name}\`.` : `Stopped "${hub.name}" (pid ${hub.pid}). It was not registered by slay.`
|
|
@@ -52619,10 +52801,10 @@ Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name
|
|
|
52619
52801
|
const backend = resolveBackend();
|
|
52620
52802
|
const hub = await findAnyHub(nameOrPort);
|
|
52621
52803
|
if (!hub) {
|
|
52622
|
-
if (backend !== "none" && (0,
|
|
52623
|
-
const root =
|
|
52624
|
-
supervisorStopQuiet(backend, nameOrPort);
|
|
52625
|
-
|
|
52804
|
+
if (backend !== "none" && (0, import_node_fs7.existsSync)(unitPath("hub", nameOrPort, backend))) {
|
|
52805
|
+
const root = readUnitRoot("hub", nameOrPort, backend);
|
|
52806
|
+
supervisorStopQuiet("hub", backend, nameOrPort);
|
|
52807
|
+
removeUnit("hub", nameOrPort, backend);
|
|
52626
52808
|
console.log(
|
|
52627
52809
|
`Removed "${nameOrPort}" (it was not running).` + (root ? ` Its data is still in ${shortenPath(root)}.` : "")
|
|
52628
52810
|
);
|
|
@@ -52639,17 +52821,18 @@ Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name
|
|
|
52639
52821
|
const hub = await requireHub(nameOrPort);
|
|
52640
52822
|
refuseSupervised(hub, "restart");
|
|
52641
52823
|
const backend = resolveBackend();
|
|
52642
|
-
if (backend === "none" || !(0,
|
|
52824
|
+
if (backend === "none" || !(0, import_node_fs7.existsSync)(unitPath("hub", hub.name, backend))) {
|
|
52643
52825
|
fail(
|
|
52644
52826
|
`"${hub.name}" is not managed by slay, so it cannot be restarted from here. Stop it where it was started, then \`slay hub create ${hub.name}\`.`
|
|
52645
52827
|
);
|
|
52646
52828
|
}
|
|
52647
52829
|
const { name, root } = hub;
|
|
52648
|
-
const logDir = ensureLogDir(root);
|
|
52830
|
+
const logDir = ensureLogDir("hub", root);
|
|
52649
52831
|
if (opts.upgrade) {
|
|
52650
|
-
const bin =
|
|
52651
|
-
|
|
52832
|
+
const bin = resolveServiceBin("hub");
|
|
52833
|
+
writeUnit(
|
|
52652
52834
|
{
|
|
52835
|
+
kind: "hub",
|
|
52653
52836
|
name,
|
|
52654
52837
|
root,
|
|
52655
52838
|
command: bin.command,
|
|
@@ -52663,9 +52846,9 @@ Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name
|
|
|
52663
52846
|
);
|
|
52664
52847
|
console.log(`Unit updated to ${HUB_PACKAGE}@${bin.version}.`);
|
|
52665
52848
|
}
|
|
52666
|
-
supervisorStop(backend, name);
|
|
52849
|
+
supervisorStop("hub", backend, name);
|
|
52667
52850
|
await waitForHubGone(hub.port, 15e3);
|
|
52668
|
-
supervisorStart(backend, name,
|
|
52851
|
+
supervisorStart("hub", backend, name, unitPath("hub", name, backend));
|
|
52669
52852
|
const back = await waitForHub(() => findHub(name), 2e4);
|
|
52670
52853
|
if (!back) fail(`Hub "${name}" did not come back within 20s. Check ${shortenPath(logDir)}.`);
|
|
52671
52854
|
console.log(`Hub "${back.name}" restarted on port ${back.port} (pid ${back.pid}).`);
|
|
@@ -52675,21 +52858,14 @@ Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name
|
|
|
52675
52858
|
const lines = Number(opts.lines);
|
|
52676
52859
|
if (!Number.isInteger(lines) || lines < 1) fail(`Invalid --lines ${opts.lines}.`);
|
|
52677
52860
|
const backend = detectBackend();
|
|
52678
|
-
|
|
52679
|
-
|
|
52680
|
-
|
|
52681
|
-
|
|
52682
|
-
|
|
52683
|
-
|
|
52684
|
-
|
|
52685
|
-
|
|
52686
|
-
if (!(0, import_node_fs6.existsSync)(logDir)) fail(`No log directory at ${shortenPath(logDir)}.`);
|
|
52687
|
-
const candidates = (0, import_node_fs6.readdirSync)(logDir).filter((f) => f.endsWith(".log")).map((f) => ({ f, path: (0, import_node_path6.join)(logDir, f), mtime: (0, import_node_fs6.statSync)((0, import_node_path6.join)(logDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
52688
|
-
if (candidates.length === 0) fail(`No log files in ${shortenPath(logDir)}.`);
|
|
52689
|
-
const target = candidates[0].path;
|
|
52690
|
-
const args = opts.follow ? ["-f", "-n", String(lines), target] : ["-n", String(lines), target];
|
|
52691
|
-
const child = (0, import_node_child_process2.spawn)("tail", args, { stdio: "inherit" });
|
|
52692
|
-
child.on("exit", (code) => process.exit(code ?? 0));
|
|
52861
|
+
followServiceLog({
|
|
52862
|
+
kind: "hub",
|
|
52863
|
+
name: hub.name,
|
|
52864
|
+
root: hub.root,
|
|
52865
|
+
lines,
|
|
52866
|
+
follow: opts.follow === true,
|
|
52867
|
+
systemdUnit: backend === "systemd" && (0, import_node_fs7.existsSync)(unitPath("hub", hub.name, backend))
|
|
52868
|
+
});
|
|
52693
52869
|
});
|
|
52694
52870
|
cmd.command("use <name|url>").description("Point this CLI at a hub \u2014 a name from `hub ls`, or a full URL").option("--token <token>", "Bearer token sent as Authorization header").action(async (target, opts) => {
|
|
52695
52871
|
const looksLikeUrl = target.includes("://");
|
|
@@ -52742,7 +52918,7 @@ Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name
|
|
|
52742
52918
|
console.log("This platform has no user service manager \u2014 no hubs can be registered.");
|
|
52743
52919
|
return;
|
|
52744
52920
|
}
|
|
52745
|
-
const units =
|
|
52921
|
+
const units = listRegisteredUnits("hub", backend);
|
|
52746
52922
|
if (units.length === 0) {
|
|
52747
52923
|
console.log("No registered hubs.");
|
|
52748
52924
|
return;
|
|
@@ -52752,7 +52928,7 @@ Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name
|
|
|
52752
52928
|
const running = live.has(u.name);
|
|
52753
52929
|
console.log(`${u.name}${running ? " (running)" : " (stopped)"}`);
|
|
52754
52930
|
console.log(` ${u.unitPath}`);
|
|
52755
|
-
const root =
|
|
52931
|
+
const root = readUnitRoot("hub", u.name, backend);
|
|
52756
52932
|
if (root) console.log(` root: ${shortenPath(root)}`);
|
|
52757
52933
|
if (!running) console.log(` start: slay hub start ${u.name}`);
|
|
52758
52934
|
}
|
|
@@ -52760,13 +52936,461 @@ Start it with \`slay hub start ${name}\`, or remove it with \`slay hub rm ${name
|
|
|
52760
52936
|
return cmd;
|
|
52761
52937
|
}
|
|
52762
52938
|
|
|
52939
|
+
// src/commands/runner.ts
|
|
52940
|
+
var import_node_child_process4 = require("node:child_process");
|
|
52941
|
+
var import_node_fs9 = require("node:fs");
|
|
52942
|
+
var import_node_path9 = require("node:path");
|
|
52943
|
+
|
|
52944
|
+
// ../../shared/platform/src/join-token.ts
|
|
52945
|
+
var JOIN_TOKEN_PREFIX = "szjt1";
|
|
52946
|
+
function decodeJoinToken(token) {
|
|
52947
|
+
const dot = token.indexOf(".");
|
|
52948
|
+
if (dot === -1 || token.slice(0, dot) !== JOIN_TOKEN_PREFIX) return null;
|
|
52949
|
+
const body = token.slice(dot + 1);
|
|
52950
|
+
if (!body) return null;
|
|
52951
|
+
try {
|
|
52952
|
+
const parsed = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
|
|
52953
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
52954
|
+
const { hubUrl, certFingerprint, secret } = parsed;
|
|
52955
|
+
if (typeof hubUrl !== "string" || typeof certFingerprint !== "string" || typeof secret !== "string") {
|
|
52956
|
+
return null;
|
|
52957
|
+
}
|
|
52958
|
+
return { hubUrl, certFingerprint, secret };
|
|
52959
|
+
} catch {
|
|
52960
|
+
return null;
|
|
52961
|
+
}
|
|
52962
|
+
}
|
|
52963
|
+
|
|
52964
|
+
// ../../shared/platform/src/slayzone-config.ts
|
|
52965
|
+
var import_node_fs8 = require("node:fs");
|
|
52966
|
+
var import_node_path8 = require("node:path");
|
|
52967
|
+
var DEFAULT_LOCAL_RUNNER_NAME = "local-runner";
|
|
52968
|
+
function getSlayzoneConfigPath() {
|
|
52969
|
+
return (0, import_node_path8.join)(getSlayzoneHomeDir(), "config.json");
|
|
52970
|
+
}
|
|
52971
|
+
function coerce(raw) {
|
|
52972
|
+
const cfg = {};
|
|
52973
|
+
if (typeof raw.runnerTransportSecret === "string" && raw.runnerTransportSecret.length > 0)
|
|
52974
|
+
cfg.runnerTransportSecret = raw.runnerTransportSecret;
|
|
52975
|
+
if (typeof raw.address === "string" && raw.address.length > 0) cfg.address = raw.address;
|
|
52976
|
+
if (typeof raw.publicAddress === "string" && raw.publicAddress.length > 0)
|
|
52977
|
+
cfg.publicAddress = raw.publicAddress;
|
|
52978
|
+
if (typeof raw.hubName === "string" && raw.hubName.trim().length > 0)
|
|
52979
|
+
cfg.hubName = raw.hubName.trim();
|
|
52980
|
+
if (typeof raw.port === "number" && Number.isInteger(raw.port)) cfg.port = raw.port;
|
|
52981
|
+
if (typeof raw.publicUrl === "string" && raw.publicUrl.length > 0) cfg.publicUrl = raw.publicUrl;
|
|
52982
|
+
if (typeof raw.joinToken === "string" && raw.joinToken.length > 0) cfg.joinToken = raw.joinToken;
|
|
52983
|
+
if (typeof raw.runnerName === "string" && raw.runnerName.length > 0)
|
|
52984
|
+
cfg.runnerName = raw.runnerName;
|
|
52985
|
+
if (typeof raw.hubUrl === "string" && raw.hubUrl.length > 0) cfg.hubUrl = raw.hubUrl;
|
|
52986
|
+
if (Array.isArray(raw.allowedRoots)) {
|
|
52987
|
+
const roots = raw.allowedRoots.filter(
|
|
52988
|
+
(r) => typeof r === "string" && r.length > 0
|
|
52989
|
+
);
|
|
52990
|
+
if (roots.length > 0) cfg.allowedRoots = roots;
|
|
52991
|
+
}
|
|
52992
|
+
if (typeof raw.pinnedCertSha256 === "string" && raw.pinnedCertSha256.length > 0)
|
|
52993
|
+
cfg.pinnedCertSha256 = raw.pinnedCertSha256;
|
|
52994
|
+
return cfg;
|
|
52995
|
+
}
|
|
52996
|
+
function loadSlayzoneConfig(configPath = getSlayzoneConfigPath()) {
|
|
52997
|
+
let raw;
|
|
52998
|
+
try {
|
|
52999
|
+
raw = (0, import_node_fs8.readFileSync)(configPath, "utf8");
|
|
53000
|
+
} catch (err) {
|
|
53001
|
+
if (err.code === "ENOENT") return {};
|
|
53002
|
+
process.stderr.write(
|
|
53003
|
+
`[slayzone-config] cannot read ${configPath}: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
|
|
53004
|
+
`
|
|
53005
|
+
);
|
|
53006
|
+
return {};
|
|
53007
|
+
}
|
|
53008
|
+
let parsed;
|
|
53009
|
+
try {
|
|
53010
|
+
parsed = JSON.parse(raw);
|
|
53011
|
+
} catch (err) {
|
|
53012
|
+
process.stderr.write(
|
|
53013
|
+
`[slayzone-config] ${configPath} is not valid JSON: ${err instanceof Error ? err.message : String(err)} \u2014 using empty config
|
|
53014
|
+
`
|
|
53015
|
+
);
|
|
53016
|
+
return {};
|
|
53017
|
+
}
|
|
53018
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
53019
|
+
process.stderr.write(`[slayzone-config] ${configPath} is not a JSON object \u2014 using empty config
|
|
53020
|
+
`);
|
|
53021
|
+
return {};
|
|
53022
|
+
}
|
|
53023
|
+
return coerce(parsed);
|
|
53024
|
+
}
|
|
53025
|
+
function saveSlayzoneConfig(cfg, configPath = getSlayzoneConfigPath()) {
|
|
53026
|
+
const dir = (0, import_node_path8.dirname)(configPath);
|
|
53027
|
+
(0, import_node_fs8.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
53028
|
+
const tmpPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
|
|
53029
|
+
(0, import_node_fs8.writeFileSync)(tmpPath, `${JSON.stringify(cfg, null, 2)}
|
|
53030
|
+
`, { mode: 384 });
|
|
53031
|
+
try {
|
|
53032
|
+
(0, import_node_fs8.renameSync)(tmpPath, configPath);
|
|
53033
|
+
} catch (err) {
|
|
53034
|
+
(0, import_node_fs8.rmSync)(tmpPath, { force: true });
|
|
53035
|
+
throw err;
|
|
53036
|
+
}
|
|
53037
|
+
}
|
|
53038
|
+
function updateSlayzoneConfig(patch, configPath = getSlayzoneConfigPath()) {
|
|
53039
|
+
const current = loadSlayzoneConfig(configPath);
|
|
53040
|
+
const merged = { ...current };
|
|
53041
|
+
for (const [k, v2] of Object.entries(patch)) {
|
|
53042
|
+
if (v2 !== void 0) merged[k] = v2;
|
|
53043
|
+
}
|
|
53044
|
+
saveSlayzoneConfig(merged, configPath);
|
|
53045
|
+
return merged;
|
|
53046
|
+
}
|
|
53047
|
+
|
|
53048
|
+
// src/commands/runner.ts
|
|
53049
|
+
var RUNNER_PACKAGE = servicePackage("runner");
|
|
53050
|
+
var ENROLL_TIMEOUT_MS = 3e4;
|
|
53051
|
+
var CONNECTED_RE = /"mode":"(enroll|hello)"/;
|
|
53052
|
+
function configPathFor(root) {
|
|
53053
|
+
return (0, import_node_path9.join)(root, "config.json");
|
|
53054
|
+
}
|
|
53055
|
+
function isEnrolled(root) {
|
|
53056
|
+
try {
|
|
53057
|
+
return (0, import_node_fs9.readdirSync)((0, import_node_path9.join)(root, "runners")).some((f) => f.endsWith(".json"));
|
|
53058
|
+
} catch {
|
|
53059
|
+
return false;
|
|
53060
|
+
}
|
|
53061
|
+
}
|
|
53062
|
+
function requireRegistered(name, backend) {
|
|
53063
|
+
if (backend === "none") {
|
|
53064
|
+
fail(
|
|
53065
|
+
`This platform has no user service manager, so runners cannot be registered here. Run one in the foreground instead:
|
|
53066
|
+
npx ${RUNNER_PACKAGE}`
|
|
53067
|
+
);
|
|
53068
|
+
}
|
|
53069
|
+
if (!(0, import_node_fs9.existsSync)(unitPath("runner", name, backend))) {
|
|
53070
|
+
const known = listRegisteredUnits("runner", backend).map((u) => u.name);
|
|
53071
|
+
fail(
|
|
53072
|
+
`No runner named "${name}" on this machine.
|
|
53073
|
+
` + (known.length > 0 ? `Registered runners: ${known.join(", ")}` : "No runners are registered. Create one with `slay runner create <name> --token <token>`.")
|
|
53074
|
+
);
|
|
53075
|
+
}
|
|
53076
|
+
const root = readUnitRoot("runner", name, backend);
|
|
53077
|
+
if (!root) {
|
|
53078
|
+
fail(
|
|
53079
|
+
`The unit for "${name}" does not record a root \u2014 it may be hand-edited. Remove it with \`slay runner rm ${name}\` and create it again.`
|
|
53080
|
+
);
|
|
53081
|
+
}
|
|
53082
|
+
return { root };
|
|
53083
|
+
}
|
|
53084
|
+
async function waitForConnected(root, timeoutMs) {
|
|
53085
|
+
const deadline = Date.now() + timeoutMs;
|
|
53086
|
+
for (; ; ) {
|
|
53087
|
+
if (CONNECTED_RE.test(readServiceLogTail("runner", root, 200))) return true;
|
|
53088
|
+
if (Date.now() >= deadline) return false;
|
|
53089
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
53090
|
+
}
|
|
53091
|
+
}
|
|
53092
|
+
async function launchRunner(args) {
|
|
53093
|
+
const { name, root, backend, creating } = args;
|
|
53094
|
+
const bin = resolveServiceBin("runner");
|
|
53095
|
+
const logDir = ensureLogDir("runner", root);
|
|
53096
|
+
const logs = serviceLogPaths("runner", root);
|
|
53097
|
+
if (backend === "none") {
|
|
53098
|
+
const out = (0, import_node_fs9.openSync)(logs.out, "a");
|
|
53099
|
+
const child = (0, import_node_child_process4.spawn)(bin.command, bin.args, {
|
|
53100
|
+
cwd: root,
|
|
53101
|
+
detached: true,
|
|
53102
|
+
stdio: ["ignore", out, out],
|
|
53103
|
+
env: {
|
|
53104
|
+
...process.env,
|
|
53105
|
+
SLAYZONE_ROOT: root,
|
|
53106
|
+
// A detached runner has no TTY, but be explicit: the first-run prompt must
|
|
53107
|
+
// never block a spawn nobody is watching.
|
|
53108
|
+
SLAYZONE_NONINTERACTIVE: "1",
|
|
53109
|
+
// Same interpreter requirement as the unit path: a dev-tree runner needs
|
|
53110
|
+
// ELECTRON_RUN_AS_NODE or its Electron-ABI natives fail to load.
|
|
53111
|
+
...bin.env ?? {}
|
|
53112
|
+
}
|
|
53113
|
+
});
|
|
53114
|
+
child.unref();
|
|
53115
|
+
if (!await waitForConnected(root, ENROLL_TIMEOUT_MS)) {
|
|
53116
|
+
failWithLog("runner", name, logs.out, `did not reach its hub within ${ENROLL_TIMEOUT_MS / 1e3}s`);
|
|
53117
|
+
}
|
|
53118
|
+
console.log(`Runner "${name}" started (pid ${child.pid ?? "?"}) and reached its hub.`);
|
|
53119
|
+
console.log(` Root: ${shortenPath(root)}`);
|
|
53120
|
+
console.log(` Logs: ${shortenPath(logDir)}`);
|
|
53121
|
+
console.log(
|
|
53122
|
+
"Note: this platform has no user service manager, so the runner will NOT restart if it crashes, and will not come back after a reboot."
|
|
53123
|
+
);
|
|
53124
|
+
return;
|
|
53125
|
+
}
|
|
53126
|
+
const writtenUnitPath = writeUnit(
|
|
53127
|
+
{
|
|
53128
|
+
kind: "runner",
|
|
53129
|
+
name,
|
|
53130
|
+
root,
|
|
53131
|
+
command: bin.command,
|
|
53132
|
+
args: bin.args,
|
|
53133
|
+
logDir,
|
|
53134
|
+
...bin.env ? { env: bin.env } : {}
|
|
53135
|
+
},
|
|
53136
|
+
backend
|
|
53137
|
+
);
|
|
53138
|
+
if (creating) console.log(`Registered ${writtenUnitPath}`);
|
|
53139
|
+
console.log(`Starting runner "${name}" (${RUNNER_PACKAGE}@${bin.version})\u2026`);
|
|
53140
|
+
try {
|
|
53141
|
+
supervisorStart("runner", backend, name, writtenUnitPath);
|
|
53142
|
+
} catch (e) {
|
|
53143
|
+
if (creating) removeUnit("runner", name, backend);
|
|
53144
|
+
if (!(e instanceof SupervisorError)) throw e;
|
|
53145
|
+
const noBus = /Failed to connect to bus|No medium found/i.test(e.output);
|
|
53146
|
+
fail(
|
|
53147
|
+
`Could not register runner "${name}" with ${backend}.
|
|
53148
|
+
|
|
53149
|
+
${e.command}
|
|
53150
|
+
${e.output || "(no output)"}
|
|
53151
|
+
|
|
53152
|
+
` + (noBus ? `systemd has no user session bus for this account, so \`systemctl --user\` cannot work. Enable a persistent user manager:
|
|
53153
|
+
sudo loginctl enable-linger ${process.env.USER ?? "<user>"}
|
|
53154
|
+
then log out and back in, and retry. If this account is not meant to have one (a container, or a root-only box), run the runner under the system manager or in the foreground instead:
|
|
53155
|
+
npx ${RUNNER_PACKAGE}
|
|
53156
|
+
` : "")
|
|
53157
|
+
);
|
|
53158
|
+
}
|
|
53159
|
+
if (!await waitForConnected(root, ENROLL_TIMEOUT_MS)) {
|
|
53160
|
+
if (creating) {
|
|
53161
|
+
supervisorStop("runner", backend, name);
|
|
53162
|
+
removeUnit("runner", name, backend);
|
|
53163
|
+
} else {
|
|
53164
|
+
supervisorStop("runner", backend, name);
|
|
53165
|
+
}
|
|
53166
|
+
failWithLog(
|
|
53167
|
+
"runner",
|
|
53168
|
+
name,
|
|
53169
|
+
logs.err,
|
|
53170
|
+
creating ? `did not reach its hub within ${ENROLL_TIMEOUT_MS / 1e3}s, so it was unregistered again` : `did not reach its hub within ${ENROLL_TIMEOUT_MS / 1e3}s (its registration was left in place)`
|
|
53171
|
+
);
|
|
53172
|
+
}
|
|
53173
|
+
const status = supervisorStatus("runner", backend, name);
|
|
53174
|
+
console.log(
|
|
53175
|
+
`Runner "${name}" running${status.pid ? ` (pid ${status.pid})` : ""} and connected to its hub.`
|
|
53176
|
+
);
|
|
53177
|
+
console.log(` Root: ${shortenPath(root)}`);
|
|
53178
|
+
console.log(` Logs: ${shortenPath(logDir)}`);
|
|
53179
|
+
console.log(` Unit: ${writtenUnitPath}`);
|
|
53180
|
+
console.log(` Runner: ${RUNNER_PACKAGE}@${bin.version}`);
|
|
53181
|
+
console.log("Restarts automatically if it crashes, and starts again when you log in.");
|
|
53182
|
+
}
|
|
53183
|
+
async function waitForStopped(name, backend, timeoutMs) {
|
|
53184
|
+
const deadline = Date.now() + timeoutMs;
|
|
53185
|
+
for (; ; ) {
|
|
53186
|
+
if (!supervisorStatus("runner", backend, name).running) return true;
|
|
53187
|
+
if (Date.now() >= deadline) return false;
|
|
53188
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
53189
|
+
}
|
|
53190
|
+
}
|
|
53191
|
+
function runnerCommand() {
|
|
53192
|
+
const cmd = new Command("runner").description("Run and manage SlayZone runners on this machine").showSuggestionAfterError(true).showHelpAfterError(true);
|
|
53193
|
+
cmd.command("ls").description("List the runners installed on this machine").option("--json", "Output as JSON").action((opts) => {
|
|
53194
|
+
const backend = detectBackend();
|
|
53195
|
+
if (backend === "none") {
|
|
53196
|
+
console.log("This platform has no user service manager \u2014 no runners can be registered.");
|
|
53197
|
+
return;
|
|
53198
|
+
}
|
|
53199
|
+
const units = listRegisteredUnits("runner", backend);
|
|
53200
|
+
const rows = units.map((u) => {
|
|
53201
|
+
const root = readUnitRoot("runner", u.name, backend);
|
|
53202
|
+
const status = supervisorStatus("runner", backend, u.name);
|
|
53203
|
+
const hubUrl = root ? loadSlayzoneConfig(configPathFor(root)).hubUrl ?? null : null;
|
|
53204
|
+
return {
|
|
53205
|
+
name: u.name,
|
|
53206
|
+
unitPath: u.unitPath,
|
|
53207
|
+
root,
|
|
53208
|
+
running: status.running,
|
|
53209
|
+
pid: status.pid,
|
|
53210
|
+
lastExit: status.lastExit,
|
|
53211
|
+
hubUrl,
|
|
53212
|
+
enrolled: root ? isEnrolled(root) : false
|
|
53213
|
+
};
|
|
53214
|
+
});
|
|
53215
|
+
if (opts.json) {
|
|
53216
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
53217
|
+
return;
|
|
53218
|
+
}
|
|
53219
|
+
if (rows.length === 0) {
|
|
53220
|
+
console.log(
|
|
53221
|
+
"No runners installed. Create one with `slay runner create <name> --token <token>`."
|
|
53222
|
+
);
|
|
53223
|
+
return;
|
|
53224
|
+
}
|
|
53225
|
+
const nameW = Math.max(4, ...rows.map((r) => r.name.length));
|
|
53226
|
+
const rootW = Math.max(4, ...rows.map((r) => r.root ? shortenPath(r.root).length : 1));
|
|
53227
|
+
const hubW = Math.max(3, ...rows.map((r) => (r.hubUrl ?? "-").length));
|
|
53228
|
+
console.log(
|
|
53229
|
+
`${"NAME".padEnd(nameW)} ${"STATE".padEnd(7)} ${"PID".padEnd(7)} ${"ROOT".padEnd(rootW)} ${"HUB".padEnd(hubW)} ENROLLED`
|
|
53230
|
+
);
|
|
53231
|
+
console.log(
|
|
53232
|
+
`${"-".repeat(nameW)} ${"-".repeat(7)} ${"-".repeat(7)} ${"-".repeat(rootW)} ${"-".repeat(hubW)} ${"-".repeat(8)}`
|
|
53233
|
+
);
|
|
53234
|
+
for (const r of rows) {
|
|
53235
|
+
console.log(
|
|
53236
|
+
`${r.name.padEnd(nameW)} ${(r.running ? "running" : "stopped").padEnd(7)} ${String(r.pid ?? "-").padEnd(7)} ${(r.root ? shortenPath(r.root) : "?").padEnd(rootW)} ${(r.hubUrl ?? "-").padEnd(hubW)} ${r.enrolled ? "yes" : "no"}`
|
|
53237
|
+
);
|
|
53238
|
+
}
|
|
53239
|
+
for (const r of rows.filter((x2) => !x2.enrolled)) {
|
|
53240
|
+
console.log(
|
|
53241
|
+
`
|
|
53242
|
+
Runner "${r.name}" has never reached a hub` + (r.lastExit !== null && r.lastExit !== 0 ? ` (last exit ${r.lastExit})` : "") + `. Check \`slay runner logs ${r.name}\`.`
|
|
53243
|
+
);
|
|
53244
|
+
}
|
|
53245
|
+
for (const r of rows.filter((x2) => !x2.running)) {
|
|
53246
|
+
console.log(`
|
|
53247
|
+
Runner "${r.name}" is stopped. Start it: slay runner start ${r.name}`);
|
|
53248
|
+
}
|
|
53249
|
+
});
|
|
53250
|
+
cmd.command("create <name>").description("Install a runner here and keep it running (crash-restart + start at login)").requiredOption("--token <token>", "Join token minted on the hub (szjt1.\u2026)").option("--root <dir>", "Runner root \u2014 its config, credentials and logs (default: cwd)").option(
|
|
53251
|
+
"--allow <dir>",
|
|
53252
|
+
"Filesystem root the runner may operate under (repeatable; default: the runner root)",
|
|
53253
|
+
(value, previous = []) => [...previous, value]
|
|
53254
|
+
).action(async (name, opts) => {
|
|
53255
|
+
const root = (0, import_node_path9.resolve)(opts.root ?? process.cwd());
|
|
53256
|
+
const backend = resolveBackend();
|
|
53257
|
+
const decoded = decodeJoinToken(opts.token);
|
|
53258
|
+
if (!decoded) {
|
|
53259
|
+
fail(
|
|
53260
|
+
`--token is not a valid SlayZone join token (expected \`szjt1.<payload>\`).
|
|
53261
|
+
Mint one on the hub:
|
|
53262
|
+
curl -X POST http://127.0.0.1:<hub-port>/api/runners/join-token \\
|
|
53263
|
+
-H 'content-type: application/json' -d '{"label":"${name}"}'`
|
|
53264
|
+
);
|
|
53265
|
+
}
|
|
53266
|
+
if (name === DEFAULT_LOCAL_RUNNER_NAME) {
|
|
53267
|
+
fail(
|
|
53268
|
+
`"${DEFAULT_LOCAL_RUNNER_NAME}" is reserved for the runner inside the SlayZone desktop app \u2014 a second runner using it would collide with that one on the hub. Choose another name.`
|
|
53269
|
+
);
|
|
53270
|
+
}
|
|
53271
|
+
if (backend !== "none" && (0, import_node_fs9.existsSync)(unitPath("runner", name, backend))) {
|
|
53272
|
+
const existingRoot = readUnitRoot("runner", name, backend);
|
|
53273
|
+
fail(
|
|
53274
|
+
`A runner named "${name}" already exists${existingRoot ? ` (root ${shortenPath(existingRoot)})` : ""}.
|
|
53275
|
+
Start it with \`slay runner start ${name}\`, or remove it with \`slay runner rm ${name}\`.`
|
|
53276
|
+
);
|
|
53277
|
+
}
|
|
53278
|
+
const occupant = loadSlayzoneConfig(configPathFor(root)).runnerName;
|
|
53279
|
+
if (occupant !== void 0) {
|
|
53280
|
+
fail(
|
|
53281
|
+
`A runner is already installed in ${shortenPath(root)} \u2014 "${occupant}".
|
|
53282
|
+
` + (occupant === name ? `Start it with \`slay runner start ${name}\`, or remove it with \`slay runner rm ${name}\`.` : `Give this runner its own directory with \`--root <dir>\`, or remove "${occupant}" first with \`slay runner rm ${occupant}\`.`)
|
|
53283
|
+
);
|
|
53284
|
+
}
|
|
53285
|
+
const allowedRoots = opts.allow && opts.allow.length > 0 ? opts.allow.map((d) => (0, import_node_path9.resolve)(d)) : [root];
|
|
53286
|
+
updateSlayzoneConfig(
|
|
53287
|
+
{
|
|
53288
|
+
joinToken: opts.token,
|
|
53289
|
+
hubUrl: decoded.hubUrl,
|
|
53290
|
+
runnerName: name,
|
|
53291
|
+
allowedRoots
|
|
53292
|
+
},
|
|
53293
|
+
configPathFor(root)
|
|
53294
|
+
);
|
|
53295
|
+
console.log(`Hub: ${decoded.hubUrl}`);
|
|
53296
|
+
console.log(`Allow: ${allowedRoots.map((d) => shortenPath(d)).join(", ")}`);
|
|
53297
|
+
await launchRunner({ name, root, backend, creating: true });
|
|
53298
|
+
});
|
|
53299
|
+
cmd.command("start <name>").description("Start an existing runner that is stopped").action(async (name) => {
|
|
53300
|
+
const backend = resolveBackend();
|
|
53301
|
+
const { root } = requireRegistered(name, backend);
|
|
53302
|
+
if (backend !== "none" && supervisorStatus("runner", backend, name).running) {
|
|
53303
|
+
const status = supervisorStatus("runner", backend, name);
|
|
53304
|
+
console.log(
|
|
53305
|
+
`Runner "${name}" is already running${status.pid ? ` (pid ${status.pid})` : ""}.`
|
|
53306
|
+
);
|
|
53307
|
+
return;
|
|
53308
|
+
}
|
|
53309
|
+
await launchRunner({ name, root, backend, creating: false });
|
|
53310
|
+
});
|
|
53311
|
+
cmd.command("stop <name>").description("Stop a runner, keeping it registered so `start` can bring it back").action(async (name) => {
|
|
53312
|
+
const backend = resolveBackend();
|
|
53313
|
+
requireRegistered(name, backend);
|
|
53314
|
+
if (backend === "none") return;
|
|
53315
|
+
supervisorStop("runner", backend, name);
|
|
53316
|
+
if (!await waitForStopped(name, backend, 15e3)) {
|
|
53317
|
+
fail(
|
|
53318
|
+
`Runner "${name}" is still running after 15s according to ${backend}. It may be managed elsewhere (docker, a system unit).`
|
|
53319
|
+
);
|
|
53320
|
+
}
|
|
53321
|
+
console.log(`Stopped "${name}". Start it again with \`slay runner start ${name}\`.`);
|
|
53322
|
+
});
|
|
53323
|
+
cmd.command("rm <name>").description("Stop a runner and remove its registration").action((name) => {
|
|
53324
|
+
const backend = resolveBackend();
|
|
53325
|
+
const { root } = requireRegistered(name, backend);
|
|
53326
|
+
if (backend === "none") return;
|
|
53327
|
+
supervisorStopQuiet("runner", backend, name);
|
|
53328
|
+
removeUnit("runner", name, backend);
|
|
53329
|
+
console.log(`Removed "${name}". Its config and credentials are still in ${shortenPath(root)}.`);
|
|
53330
|
+
console.log(
|
|
53331
|
+
`It is still enrolled on its hub \u2014 revoke it there if this machine is going away.`
|
|
53332
|
+
);
|
|
53333
|
+
});
|
|
53334
|
+
cmd.command("restart <name>").description("Restart a runner").option("--upgrade", `Re-resolve ${RUNNER_PACKAGE} first (picks up a newer version)`).action(async (name, opts) => {
|
|
53335
|
+
const backend = resolveBackend();
|
|
53336
|
+
const { root } = requireRegistered(name, backend);
|
|
53337
|
+
if (backend === "none") return;
|
|
53338
|
+
const logDir = ensureLogDir("runner", root);
|
|
53339
|
+
if (opts.upgrade) {
|
|
53340
|
+
const bin = resolveServiceBin("runner");
|
|
53341
|
+
writeUnit(
|
|
53342
|
+
{
|
|
53343
|
+
kind: "runner",
|
|
53344
|
+
name,
|
|
53345
|
+
root,
|
|
53346
|
+
command: bin.command,
|
|
53347
|
+
args: bin.args,
|
|
53348
|
+
logDir,
|
|
53349
|
+
// Must carry the interpreter env: dropping it here would rewrite a
|
|
53350
|
+
// working unit into one that crash-loops on Electron-ABI natives.
|
|
53351
|
+
...bin.env ? { env: bin.env } : {}
|
|
53352
|
+
},
|
|
53353
|
+
backend
|
|
53354
|
+
);
|
|
53355
|
+
console.log(`Unit updated to ${RUNNER_PACKAGE}@${bin.version}.`);
|
|
53356
|
+
}
|
|
53357
|
+
supervisorStop("runner", backend, name);
|
|
53358
|
+
await waitForStopped(name, backend, 15e3);
|
|
53359
|
+
supervisorStart("runner", backend, name, unitPath("runner", name, backend));
|
|
53360
|
+
if (!await waitForConnected(root, ENROLL_TIMEOUT_MS)) {
|
|
53361
|
+
fail(
|
|
53362
|
+
`Runner "${name}" did not reconnect within ${ENROLL_TIMEOUT_MS / 1e3}s. Check \`slay runner logs ${name}\`.`
|
|
53363
|
+
);
|
|
53364
|
+
}
|
|
53365
|
+
const status = supervisorStatus("runner", backend, name);
|
|
53366
|
+
console.log(
|
|
53367
|
+
`Runner "${name}" restarted${status.pid ? ` (pid ${status.pid})` : ""} and reconnected.`
|
|
53368
|
+
);
|
|
53369
|
+
});
|
|
53370
|
+
cmd.command("logs <name>").description("Show a runner's log output").option("-n, --lines <n>", "Last N lines", "50").option("-f, --follow", "Follow the log").action((name, opts) => {
|
|
53371
|
+
const backend = detectBackend();
|
|
53372
|
+
const { root } = requireRegistered(name, backend);
|
|
53373
|
+
const lines = Number(opts.lines);
|
|
53374
|
+
if (!Number.isInteger(lines) || lines < 1) fail(`Invalid --lines ${opts.lines}.`);
|
|
53375
|
+
followServiceLog({
|
|
53376
|
+
kind: "runner",
|
|
53377
|
+
name,
|
|
53378
|
+
root,
|
|
53379
|
+
lines,
|
|
53380
|
+
follow: opts.follow === true,
|
|
53381
|
+
systemdUnit: backend === "systemd" && (0, import_node_fs9.existsSync)(unitPath("runner", name, backend))
|
|
53382
|
+
});
|
|
53383
|
+
});
|
|
53384
|
+
return cmd;
|
|
53385
|
+
}
|
|
53386
|
+
|
|
52763
53387
|
// src/index.ts
|
|
52764
53388
|
process.removeAllListeners("warning");
|
|
52765
53389
|
process.on("warning", (warning) => {
|
|
52766
53390
|
if (warning.name === "ExperimentalWarning" && /SQLite/i.test(warning.message)) return;
|
|
52767
53391
|
console.warn(`${warning.name}: ${warning.message}`);
|
|
52768
53392
|
});
|
|
52769
|
-
var program2 = new Command().name("slay").description("SlayZone CLI").version("0.36.0-beta.
|
|
53393
|
+
var program2 = new Command().name("slay").description("SlayZone CLI").version("0.36.0-beta.11").option("--dev", "Use development database (slayzone.dev.sqlite)").option("--hub <name|port>", "Target a specific hub on this machine (see `slay hub ls`)").showSuggestionAfterError(true).showHelpAfterError(true).hook("preAction", async (thisCommand) => {
|
|
52770
53394
|
const root = thisCommand.parent ?? thisCommand;
|
|
52771
53395
|
const opts = root.opts();
|
|
52772
53396
|
if (opts.dev) process.env.SLAYZONE_DEV = "1";
|
|
@@ -52803,6 +53427,7 @@ program2.addCommand(completionsCommand());
|
|
|
52803
53427
|
program2.addCommand(initCommand());
|
|
52804
53428
|
program2.addCommand(ptyCommand());
|
|
52805
53429
|
program2.addCommand(hubCommand());
|
|
53430
|
+
program2.addCommand(runnerCommand());
|
|
52806
53431
|
program2.parseAsync(process.argv).catch((err) => {
|
|
52807
53432
|
console.error(err instanceof Error ? err.message : String(err));
|
|
52808
53433
|
process.exit(1);
|