@opentag/cli 0.3.3 → 0.3.5
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/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/index.js +364 -50
- package/dist/index.js.map +1 -1
- package/dist/service.d.ts +15 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/setup/flow.d.ts +1 -0
- package/dist/setup/flow.d.ts.map +1 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -2667,6 +2667,28 @@ var serviceHardeningEnvKeys = [
|
|
|
2667
2667
|
"OPENTAG_RATE_LIMIT_MAX_REQUESTS",
|
|
2668
2668
|
"OPENTAG_RATE_LIMIT_DISABLED"
|
|
2669
2669
|
];
|
|
2670
|
+
var launchAgentCliPath = [
|
|
2671
|
+
"/opt/homebrew/bin",
|
|
2672
|
+
"/opt/homebrew/sbin",
|
|
2673
|
+
"/usr/local/bin",
|
|
2674
|
+
"/usr/bin",
|
|
2675
|
+
"/bin",
|
|
2676
|
+
"/usr/sbin",
|
|
2677
|
+
"/sbin"
|
|
2678
|
+
].join(":");
|
|
2679
|
+
function linuxServiceCliPath(home) {
|
|
2680
|
+
return [
|
|
2681
|
+
join3(home, ".local", "bin"),
|
|
2682
|
+
join3(home, ".npm-global", "bin"),
|
|
2683
|
+
join3(home, ".bun", "bin"),
|
|
2684
|
+
"/usr/local/bin",
|
|
2685
|
+
"/usr/bin",
|
|
2686
|
+
"/bin",
|
|
2687
|
+
"/usr/local/sbin",
|
|
2688
|
+
"/usr/sbin",
|
|
2689
|
+
"/sbin"
|
|
2690
|
+
].join(":");
|
|
2691
|
+
}
|
|
2670
2692
|
function loggerFrom(dependencies) {
|
|
2671
2693
|
return dependencies.logger ?? console;
|
|
2672
2694
|
}
|
|
@@ -2679,6 +2701,14 @@ function homeFrom(dependencies) {
|
|
|
2679
2701
|
function uidFrom(dependencies) {
|
|
2680
2702
|
return dependencies.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0);
|
|
2681
2703
|
}
|
|
2704
|
+
function serviceControllerForPlatform(platform = process.platform) {
|
|
2705
|
+
if (platform === "darwin") return "launchd";
|
|
2706
|
+
if (platform === "linux") return "systemd";
|
|
2707
|
+
return "unsupported";
|
|
2708
|
+
}
|
|
2709
|
+
function serviceControllerFrom(dependencies) {
|
|
2710
|
+
return serviceControllerForPlatform(platformFrom(dependencies));
|
|
2711
|
+
}
|
|
2682
2712
|
function servicePaths(options = {}, dependencies = {}) {
|
|
2683
2713
|
const home = homeFrom(dependencies);
|
|
2684
2714
|
const env = dependencies.env ?? process.env;
|
|
@@ -2691,7 +2721,8 @@ function servicePaths(options = {}, dependencies = {}) {
|
|
|
2691
2721
|
logsDir,
|
|
2692
2722
|
plistPath: join3(home, "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`),
|
|
2693
2723
|
stdoutPath: join3(logsDir, "opentag.log"),
|
|
2694
|
-
stderrPath: join3(logsDir, "opentag.err.log")
|
|
2724
|
+
stderrPath: join3(logsDir, "opentag.err.log"),
|
|
2725
|
+
unitPath: join3(home, ".config", "systemd", "user", `${SERVICE_LABEL}.service`)
|
|
2695
2726
|
};
|
|
2696
2727
|
}
|
|
2697
2728
|
function escapeXml(value) {
|
|
@@ -2741,6 +2772,36 @@ function buildLaunchAgentPlist(input) {
|
|
|
2741
2772
|
""
|
|
2742
2773
|
].join("\n");
|
|
2743
2774
|
}
|
|
2775
|
+
function systemdQuote(value) {
|
|
2776
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("$", "\\$").replaceAll("%", "%%")}"`;
|
|
2777
|
+
}
|
|
2778
|
+
function systemdPathValue(value) {
|
|
2779
|
+
return value.replaceAll("%", "%%");
|
|
2780
|
+
}
|
|
2781
|
+
function buildSystemdUserService(input) {
|
|
2782
|
+
const environment = Object.entries(input.environment ?? {}).map(
|
|
2783
|
+
([key, value]) => `Environment=${systemdQuote(`${key}=${value}`)}`
|
|
2784
|
+
);
|
|
2785
|
+
return [
|
|
2786
|
+
"[Unit]",
|
|
2787
|
+
"Description=OpenTag local agent",
|
|
2788
|
+
"After=network.target",
|
|
2789
|
+
"",
|
|
2790
|
+
"[Service]",
|
|
2791
|
+
"Type=simple",
|
|
2792
|
+
`WorkingDirectory=${systemdQuote(input.workingDirectory)}`,
|
|
2793
|
+
...environment,
|
|
2794
|
+
`ExecStart=${input.execStart.map(systemdQuote).join(" ")}`,
|
|
2795
|
+
"Restart=always",
|
|
2796
|
+
"RestartSec=3",
|
|
2797
|
+
`StandardOutput=append:${systemdPathValue(input.stdoutPath)}`,
|
|
2798
|
+
`StandardError=append:${systemdPathValue(input.stderrPath)}`,
|
|
2799
|
+
"",
|
|
2800
|
+
"[Install]",
|
|
2801
|
+
"WantedBy=default.target",
|
|
2802
|
+
""
|
|
2803
|
+
].join("\n");
|
|
2804
|
+
}
|
|
2744
2805
|
function launchctlRunner(dependencies) {
|
|
2745
2806
|
if (dependencies.launchctl) return dependencies.launchctl;
|
|
2746
2807
|
return (args) => {
|
|
@@ -2752,6 +2813,17 @@ function launchctlRunner(dependencies) {
|
|
|
2752
2813
|
};
|
|
2753
2814
|
};
|
|
2754
2815
|
}
|
|
2816
|
+
function systemctlRunner(dependencies) {
|
|
2817
|
+
if (dependencies.systemctl) return dependencies.systemctl;
|
|
2818
|
+
return (args) => {
|
|
2819
|
+
const result = spawnSync("systemctl", ["--user", ...args], { encoding: "utf8" });
|
|
2820
|
+
return {
|
|
2821
|
+
status: result.status ?? 1,
|
|
2822
|
+
stdout: result.stdout ?? "",
|
|
2823
|
+
stderr: result.stderr ?? ""
|
|
2824
|
+
};
|
|
2825
|
+
};
|
|
2826
|
+
}
|
|
2755
2827
|
function launchdDomain(dependencies) {
|
|
2756
2828
|
return `gui/${uidFrom(dependencies)}`;
|
|
2757
2829
|
}
|
|
@@ -2759,21 +2831,99 @@ function launchdServiceTarget(dependencies) {
|
|
|
2759
2831
|
return `${launchdDomain(dependencies)}/${SERVICE_LABEL}`;
|
|
2760
2832
|
}
|
|
2761
2833
|
function unsupportedMessage() {
|
|
2762
|
-
return "OpenTag service management is
|
|
2834
|
+
return "OpenTag service management is supported on macOS and Linux only. Use `opentag start` in the foreground on this platform.";
|
|
2763
2835
|
}
|
|
2764
|
-
function
|
|
2765
|
-
|
|
2836
|
+
function assertSupportedServiceController(dependencies) {
|
|
2837
|
+
const controller = serviceControllerFrom(dependencies);
|
|
2838
|
+
if (controller === "unsupported") {
|
|
2766
2839
|
throw new Error(unsupportedMessage());
|
|
2767
2840
|
}
|
|
2841
|
+
return controller;
|
|
2768
2842
|
}
|
|
2769
2843
|
function runLaunchctlOrThrow(dependencies, args, action) {
|
|
2770
2844
|
const result = launchctlRunner(dependencies)(args);
|
|
2771
2845
|
if (result.status !== 0) {
|
|
2772
|
-
const detail =
|
|
2846
|
+
const detail = launchctlDetail(result);
|
|
2847
|
+
throw new Error(`${action} failed${detail ? `: ${detail}` : "."}`);
|
|
2848
|
+
}
|
|
2849
|
+
return result;
|
|
2850
|
+
}
|
|
2851
|
+
function launchctlDetail(result) {
|
|
2852
|
+
return [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join("\n");
|
|
2853
|
+
}
|
|
2854
|
+
function systemctlDetail(result) {
|
|
2855
|
+
return [result.stderr.trim(), result.stdout.trim()].filter(Boolean).join("\n");
|
|
2856
|
+
}
|
|
2857
|
+
function runSystemctlOrThrow(dependencies, args, action) {
|
|
2858
|
+
const result = systemctlRunner(dependencies)(args);
|
|
2859
|
+
if (result.status !== 0) {
|
|
2860
|
+
const detail = systemctlDetail(result);
|
|
2773
2861
|
throw new Error(`${action} failed${detail ? `: ${detail}` : "."}`);
|
|
2774
2862
|
}
|
|
2775
2863
|
return result;
|
|
2776
2864
|
}
|
|
2865
|
+
function printLaunchdService(dependencies) {
|
|
2866
|
+
return launchctlRunner(dependencies)(["print", launchdServiceTarget(dependencies)]);
|
|
2867
|
+
}
|
|
2868
|
+
function systemdUnitName() {
|
|
2869
|
+
return `${SERVICE_LABEL}.service`;
|
|
2870
|
+
}
|
|
2871
|
+
function printSystemdService(dependencies) {
|
|
2872
|
+
return systemctlRunner(dependencies)(["is-active", systemdUnitName()]);
|
|
2873
|
+
}
|
|
2874
|
+
function sleepFrom(dependencies) {
|
|
2875
|
+
return dependencies.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
|
|
2876
|
+
}
|
|
2877
|
+
async function waitForLaunchdLoaded(dependencies, input = {}) {
|
|
2878
|
+
const intervalMs = input.intervalMs ?? 100;
|
|
2879
|
+
const deadline = Date.now() + (input.timeoutMs ?? 1500);
|
|
2880
|
+
while (true) {
|
|
2881
|
+
if (printLaunchdService(dependencies).status === 0) return true;
|
|
2882
|
+
if (Date.now() >= deadline) return false;
|
|
2883
|
+
await sleepFrom(dependencies)(intervalMs);
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
async function waitForLaunchdUnloaded(dependencies, input = {}) {
|
|
2887
|
+
const intervalMs = input.intervalMs ?? 100;
|
|
2888
|
+
const deadline = Date.now() + (input.timeoutMs ?? 1500);
|
|
2889
|
+
while (true) {
|
|
2890
|
+
if (printLaunchdService(dependencies).status !== 0) return true;
|
|
2891
|
+
if (Date.now() >= deadline) return false;
|
|
2892
|
+
await sleepFrom(dependencies)(intervalMs);
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
async function waitForSystemdActive(dependencies, input = {}) {
|
|
2896
|
+
const intervalMs = input.intervalMs ?? 100;
|
|
2897
|
+
const deadline = Date.now() + (input.timeoutMs ?? 1500);
|
|
2898
|
+
while (true) {
|
|
2899
|
+
const result = printSystemdService(dependencies);
|
|
2900
|
+
if (result.status === 0 && result.stdout.trim() === "active") return true;
|
|
2901
|
+
if (Date.now() >= deadline) return false;
|
|
2902
|
+
await sleepFrom(dependencies)(intervalMs);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
async function waitForSystemdInactive(dependencies, input = {}) {
|
|
2906
|
+
const intervalMs = input.intervalMs ?? 100;
|
|
2907
|
+
const deadline = Date.now() + (input.timeoutMs ?? 1500);
|
|
2908
|
+
while (true) {
|
|
2909
|
+
const result = printSystemdService(dependencies);
|
|
2910
|
+
if (result.status !== 0 || result.stdout.trim() !== "active") return true;
|
|
2911
|
+
if (Date.now() >= deadline) return false;
|
|
2912
|
+
await sleepFrom(dependencies)(intervalMs);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
async function waitForServiceLoaded(dependencies, input = {}) {
|
|
2916
|
+
const controller = serviceControllerFrom(dependencies);
|
|
2917
|
+
if (controller === "launchd") return waitForLaunchdLoaded(dependencies, input);
|
|
2918
|
+
if (controller === "systemd") return waitForSystemdActive(dependencies, input);
|
|
2919
|
+
return false;
|
|
2920
|
+
}
|
|
2921
|
+
async function waitForServiceUnloaded(dependencies, input = {}) {
|
|
2922
|
+
const controller = serviceControllerFrom(dependencies);
|
|
2923
|
+
if (controller === "launchd") return waitForLaunchdUnloaded(dependencies, input);
|
|
2924
|
+
if (controller === "systemd") return waitForSystemdInactive(dependencies, input);
|
|
2925
|
+
return true;
|
|
2926
|
+
}
|
|
2777
2927
|
function serviceWorkingDirectory(configPath) {
|
|
2778
2928
|
const config = readCliConfig(configPath);
|
|
2779
2929
|
return config.daemon.repositories[0]?.checkoutPath ?? dirname2(configPath);
|
|
@@ -2847,95 +2997,168 @@ function formatConnectorReadiness(config) {
|
|
|
2847
2997
|
}
|
|
2848
2998
|
return lines.length > 1 ? lines : [...lines, " none configured"];
|
|
2849
2999
|
}
|
|
2850
|
-
function
|
|
3000
|
+
function serviceCliPath(dependencies) {
|
|
3001
|
+
const controller = serviceControllerFrom(dependencies);
|
|
3002
|
+
return controller === "systemd" ? linuxServiceCliPath(homeFrom(dependencies)) : launchAgentCliPath;
|
|
3003
|
+
}
|
|
3004
|
+
function serviceEnvironment(options, paths, dependencies) {
|
|
2851
3005
|
return {
|
|
2852
3006
|
OPENTAG_CONFIG_PATH: paths.configPath,
|
|
3007
|
+
PATH: serviceCliPath(dependencies),
|
|
2853
3008
|
...serviceHardeningEnvironment(options)
|
|
2854
3009
|
};
|
|
2855
3010
|
}
|
|
2856
3011
|
function installService(options = {}, dependencies = {}) {
|
|
2857
|
-
|
|
3012
|
+
const controller = assertSupportedServiceController(dependencies);
|
|
2858
3013
|
const paths = servicePaths(options, dependencies);
|
|
2859
3014
|
const workingDirectory = serviceWorkingDirectory(paths.configPath);
|
|
2860
|
-
mkdirSync2(dirname2(paths.plistPath), { recursive: true });
|
|
2861
3015
|
ensurePrivateDirectory(paths.logsDir);
|
|
2862
|
-
|
|
3016
|
+
if (controller === "launchd") {
|
|
3017
|
+
mkdirSync2(dirname2(paths.plistPath), { recursive: true });
|
|
3018
|
+
const plist = buildLaunchAgentPlist({
|
|
3019
|
+
label: paths.label,
|
|
3020
|
+
programArguments: serviceProgramArguments(options, dependencies),
|
|
3021
|
+
runAtLoad: true,
|
|
3022
|
+
keepAlive: true,
|
|
3023
|
+
stdoutPath: paths.stdoutPath,
|
|
3024
|
+
stderrPath: paths.stderrPath,
|
|
3025
|
+
workingDirectory,
|
|
3026
|
+
environment: serviceEnvironment(options, paths, dependencies)
|
|
3027
|
+
});
|
|
3028
|
+
writeFileSync2(paths.plistPath, plist, { mode: 420 });
|
|
3029
|
+
return paths;
|
|
3030
|
+
}
|
|
3031
|
+
mkdirSync2(dirname2(paths.unitPath), { recursive: true });
|
|
3032
|
+
const unit = buildSystemdUserService({
|
|
2863
3033
|
label: paths.label,
|
|
2864
|
-
|
|
2865
|
-
runAtLoad: true,
|
|
2866
|
-
keepAlive: true,
|
|
3034
|
+
execStart: serviceProgramArguments(options, dependencies),
|
|
2867
3035
|
stdoutPath: paths.stdoutPath,
|
|
2868
3036
|
stderrPath: paths.stderrPath,
|
|
2869
3037
|
workingDirectory,
|
|
2870
|
-
environment:
|
|
3038
|
+
environment: serviceEnvironment(options, paths, dependencies)
|
|
2871
3039
|
});
|
|
2872
|
-
writeFileSync2(paths.
|
|
3040
|
+
writeFileSync2(paths.unitPath, unit, { mode: 420 });
|
|
3041
|
+
runSystemctlOrThrow(dependencies, ["daemon-reload"], "systemctl --user daemon-reload");
|
|
3042
|
+
runSystemctlOrThrow(dependencies, ["enable", systemdUnitName()], "systemctl --user enable");
|
|
2873
3043
|
return paths;
|
|
2874
3044
|
}
|
|
2875
|
-
function installed(paths) {
|
|
2876
|
-
return existsSync2(paths.plistPath);
|
|
3045
|
+
function installed(paths, controller) {
|
|
3046
|
+
if (controller === "launchd") return existsSync2(paths.plistPath);
|
|
3047
|
+
if (controller === "systemd") return existsSync2(paths.unitPath);
|
|
3048
|
+
return false;
|
|
2877
3049
|
}
|
|
2878
3050
|
function isNotLoaded(result) {
|
|
2879
3051
|
const text2 = `${result.stderr}
|
|
2880
3052
|
${result.stdout}`.toLowerCase();
|
|
2881
3053
|
return text2.includes("no such process") || text2.includes("could not find service") || text2.includes("service is not loaded");
|
|
2882
3054
|
}
|
|
3055
|
+
function isSystemdNotLoaded(result) {
|
|
3056
|
+
const text2 = `${result.stderr}
|
|
3057
|
+
${result.stdout}`.toLowerCase();
|
|
3058
|
+
return text2.includes("could not be found") || text2.includes("not loaded") || text2.includes("not-found") || text2.includes("no such");
|
|
3059
|
+
}
|
|
2883
3060
|
function startService(options = {}, dependencies = {}) {
|
|
2884
|
-
|
|
3061
|
+
const controller = assertSupportedServiceController(dependencies);
|
|
2885
3062
|
const paths = servicePaths(options, dependencies);
|
|
2886
|
-
if (!installed(paths)) {
|
|
3063
|
+
if (!installed(paths, controller)) {
|
|
2887
3064
|
throw new Error(`OpenTag service is not installed. Run \`opentag service install --config ${paths.configPath}\` first.`);
|
|
2888
3065
|
}
|
|
3066
|
+
if (controller === "systemd") {
|
|
3067
|
+
runSystemctlOrThrow(dependencies, ["daemon-reload"], "systemctl --user daemon-reload");
|
|
3068
|
+
runSystemctlOrThrow(dependencies, ["start", systemdUnitName()], "systemctl --user start");
|
|
3069
|
+
return paths;
|
|
3070
|
+
}
|
|
2889
3071
|
const launchctl = launchctlRunner(dependencies);
|
|
2890
3072
|
const bootstrap = launchctl(["bootstrap", launchdDomain(dependencies), paths.plistPath]);
|
|
2891
3073
|
if (bootstrap.status !== 0) {
|
|
2892
|
-
const print =
|
|
3074
|
+
const print = printLaunchdService(dependencies);
|
|
2893
3075
|
if (print.status !== 0) {
|
|
2894
|
-
const detail =
|
|
3076
|
+
const detail = launchctlDetail(bootstrap);
|
|
2895
3077
|
throw new Error(`launchctl bootstrap failed${detail ? `: ${detail}` : "."}`);
|
|
2896
3078
|
}
|
|
2897
3079
|
}
|
|
2898
|
-
|
|
3080
|
+
const kickstart = launchctl(["kickstart", "-k", launchdServiceTarget(dependencies)]);
|
|
3081
|
+
if (kickstart.status !== 0 && printLaunchdService(dependencies).status !== 0) {
|
|
3082
|
+
const detail = launchctlDetail(kickstart);
|
|
3083
|
+
throw new Error(`launchctl kickstart failed${detail ? `: ${detail}` : "."}`);
|
|
3084
|
+
}
|
|
2899
3085
|
return paths;
|
|
2900
3086
|
}
|
|
2901
3087
|
function stopService(options = {}, dependencies = {}) {
|
|
2902
|
-
|
|
3088
|
+
const controller = assertSupportedServiceController(dependencies);
|
|
2903
3089
|
const paths = servicePaths(options, dependencies);
|
|
2904
|
-
if (!installed(paths)) return paths;
|
|
3090
|
+
if (!installed(paths, controller)) return paths;
|
|
3091
|
+
if (controller === "systemd") {
|
|
3092
|
+
const result = systemctlRunner(dependencies)(["stop", systemdUnitName()]);
|
|
3093
|
+
if (result.status !== 0 && !isSystemdNotLoaded(result)) {
|
|
3094
|
+
const detail = systemctlDetail(result);
|
|
3095
|
+
throw new Error(`systemctl --user stop failed${detail ? `: ${detail}` : "."}`);
|
|
3096
|
+
}
|
|
3097
|
+
return paths;
|
|
3098
|
+
}
|
|
2905
3099
|
const launchctl = launchctlRunner(dependencies);
|
|
2906
3100
|
const first = launchctl(["bootout", launchdServiceTarget(dependencies)]);
|
|
2907
|
-
if (first.status !== 0
|
|
3101
|
+
if (first.status !== 0) {
|
|
2908
3102
|
const second = launchctl(["bootout", launchdDomain(dependencies), paths.plistPath]);
|
|
2909
3103
|
if (second.status !== 0 && !isNotLoaded(second)) {
|
|
2910
|
-
const
|
|
3104
|
+
const firstDetail = isNotLoaded(first) ? "" : launchctlDetail(first);
|
|
3105
|
+
const detail = [launchctlDetail(second), firstDetail].filter(Boolean).join("\n");
|
|
2911
3106
|
throw new Error(`launchctl bootout failed${detail ? `: ${detail}` : "."}`);
|
|
2912
3107
|
}
|
|
2913
3108
|
}
|
|
2914
3109
|
return paths;
|
|
2915
3110
|
}
|
|
2916
3111
|
function uninstallService(options = {}, dependencies = {}) {
|
|
2917
|
-
|
|
3112
|
+
const controller = assertSupportedServiceController(dependencies);
|
|
2918
3113
|
const paths = stopService(options, dependencies);
|
|
2919
|
-
|
|
3114
|
+
if (controller === "launchd") {
|
|
3115
|
+
rmSync2(paths.plistPath, { force: true });
|
|
3116
|
+
return paths;
|
|
3117
|
+
}
|
|
3118
|
+
const disabled = systemctlRunner(dependencies)(["disable", systemdUnitName()]);
|
|
3119
|
+
if (disabled.status !== 0 && !isSystemdNotLoaded(disabled)) {
|
|
3120
|
+
const detail = systemctlDetail(disabled);
|
|
3121
|
+
throw new Error(`systemctl --user disable failed${detail ? `: ${detail}` : "."}`);
|
|
3122
|
+
}
|
|
3123
|
+
rmSync2(paths.unitPath, { force: true });
|
|
3124
|
+
runSystemctlOrThrow(dependencies, ["daemon-reload"], "systemctl --user daemon-reload");
|
|
2920
3125
|
return paths;
|
|
2921
3126
|
}
|
|
2922
3127
|
function enableServiceAutostart(options = {}, dependencies = {}) {
|
|
2923
|
-
|
|
2924
|
-
const
|
|
3128
|
+
const controller = assertSupportedServiceController(dependencies);
|
|
3129
|
+
const candidate = servicePaths(options, dependencies);
|
|
3130
|
+
const paths = installed(candidate, controller) ? candidate : installService(options, dependencies);
|
|
3131
|
+
if (controller === "systemd") {
|
|
3132
|
+
runSystemctlOrThrow(dependencies, ["enable", systemdUnitName()], "systemctl --user enable");
|
|
3133
|
+
return paths;
|
|
3134
|
+
}
|
|
2925
3135
|
runLaunchctlOrThrow(dependencies, ["enable", launchdServiceTarget(dependencies)], "launchctl enable");
|
|
2926
3136
|
return paths;
|
|
2927
3137
|
}
|
|
2928
3138
|
function disableServiceAutostart(options = {}, dependencies = {}) {
|
|
2929
|
-
|
|
3139
|
+
const controller = assertSupportedServiceController(dependencies);
|
|
2930
3140
|
const paths = servicePaths(options, dependencies);
|
|
2931
|
-
if (installed(paths)) {
|
|
3141
|
+
if (installed(paths, controller)) {
|
|
3142
|
+
if (controller === "systemd") {
|
|
3143
|
+
runSystemctlOrThrow(dependencies, ["disable", systemdUnitName()], "systemctl --user disable");
|
|
3144
|
+
return paths;
|
|
3145
|
+
}
|
|
2932
3146
|
runLaunchctlOrThrow(dependencies, ["disable", launchdServiceTarget(dependencies)], "launchctl disable");
|
|
2933
3147
|
}
|
|
2934
3148
|
return paths;
|
|
2935
3149
|
}
|
|
2936
3150
|
function serviceAutostart(paths, dependencies, isInstalled) {
|
|
2937
3151
|
if (!isInstalled) return "disabled";
|
|
2938
|
-
|
|
3152
|
+
const controller = serviceControllerFrom(dependencies);
|
|
3153
|
+
if (controller === "systemd") {
|
|
3154
|
+
const result2 = systemctlRunner(dependencies)(["is-enabled", systemdUnitName()]);
|
|
3155
|
+
const text2 = `${result2.stdout}
|
|
3156
|
+
${result2.stderr}`.trim().toLowerCase();
|
|
3157
|
+
if (result2.status === 0 && text2.includes("enabled")) return "enabled";
|
|
3158
|
+
if (text2.includes("disabled") || text2.includes("not-found") || text2.includes("could not be found")) return "disabled";
|
|
3159
|
+
return "unknown";
|
|
3160
|
+
}
|
|
3161
|
+
if (controller !== "launchd") return "unknown";
|
|
2939
3162
|
const result = launchctlRunner(dependencies)(["print-disabled", launchdDomain(dependencies)]);
|
|
2940
3163
|
if (result.status !== 0) return "unknown";
|
|
2941
3164
|
const escapedLabel = SERVICE_LABEL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -2947,12 +3170,15 @@ function serviceAutostart(paths, dependencies, isInstalled) {
|
|
|
2947
3170
|
}
|
|
2948
3171
|
function getServiceStatus(options = {}, dependencies = {}) {
|
|
2949
3172
|
const paths = servicePaths(options, dependencies);
|
|
2950
|
-
const controller =
|
|
2951
|
-
const isInstalled = installed(paths);
|
|
3173
|
+
const controller = serviceControllerFrom(dependencies);
|
|
3174
|
+
const isInstalled = installed(paths, controller);
|
|
2952
3175
|
let running = isInstalled ? "stopped" : "unknown";
|
|
2953
3176
|
if (controller === "launchd" && isInstalled) {
|
|
2954
3177
|
const result = launchctlRunner(dependencies)(["print", launchdServiceTarget(dependencies)]);
|
|
2955
3178
|
running = result.status === 0 ? "running" : "stopped";
|
|
3179
|
+
} else if (controller === "systemd" && isInstalled) {
|
|
3180
|
+
const result = systemctlRunner(dependencies)(["is-active", systemdUnitName()]);
|
|
3181
|
+
running = result.status === 0 && result.stdout.trim() === "active" ? "running" : "stopped";
|
|
2956
3182
|
}
|
|
2957
3183
|
let runtimeMode = "unknown";
|
|
2958
3184
|
let relayUrl;
|
|
@@ -3003,10 +3229,34 @@ function readLaunchAgentEnvironment(paths) {
|
|
|
3003
3229
|
})
|
|
3004
3230
|
);
|
|
3005
3231
|
}
|
|
3232
|
+
function unescapeSystemdQuotedValue(value) {
|
|
3233
|
+
return value.replaceAll("%%", "%").replaceAll('\\"', '"').replaceAll("\\$", "$").replaceAll("\\\\", "\\");
|
|
3234
|
+
}
|
|
3235
|
+
function readSystemdEnvironment(paths) {
|
|
3236
|
+
if (!existsSync2(paths.unitPath)) return {};
|
|
3237
|
+
const unit = readFileSync2(paths.unitPath, "utf8");
|
|
3238
|
+
return Object.fromEntries(
|
|
3239
|
+
unit.split(/\r?\n/).flatMap((line) => {
|
|
3240
|
+
const match = line.match(/^Environment=(?:"((?:\\.|[^"])*)"|(.+))$/);
|
|
3241
|
+
const raw = match?.[1] ?? match?.[2];
|
|
3242
|
+
if (!raw) return [];
|
|
3243
|
+
const value = unescapeSystemdQuotedValue(raw);
|
|
3244
|
+
const separator = value.indexOf("=");
|
|
3245
|
+
if (separator <= 0) return [];
|
|
3246
|
+
return [[value.slice(0, separator), value.slice(separator + 1)]];
|
|
3247
|
+
})
|
|
3248
|
+
);
|
|
3249
|
+
}
|
|
3250
|
+
function readServiceEnvironment(paths) {
|
|
3251
|
+
return {
|
|
3252
|
+
...readLaunchAgentEnvironment(paths),
|
|
3253
|
+
...readSystemdEnvironment(paths)
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3006
3256
|
function formatServiceHardening(paths) {
|
|
3007
|
-
const environment =
|
|
3257
|
+
const environment = readServiceEnvironment(paths);
|
|
3008
3258
|
const configured2 = serviceHardeningEnvKeys.filter((key) => environment[key]).map((key) => ` ${key}=${environment[key]}`);
|
|
3009
|
-
return ["Service Hardening:", ...configured2.length ? configured2 : [" dispatcher hardening env not configured in
|
|
3259
|
+
return ["Service Hardening:", ...configured2.length ? configured2 : [" dispatcher hardening env not configured in service definition"]];
|
|
3010
3260
|
}
|
|
3011
3261
|
function doctorCounts(checks) {
|
|
3012
3262
|
return {
|
|
@@ -3096,6 +3346,7 @@ async function getServiceStatusWithRuntimeReadiness(options = {}, dependencies =
|
|
|
3096
3346
|
}
|
|
3097
3347
|
}
|
|
3098
3348
|
function formatServiceStatus(summary) {
|
|
3349
|
+
const definitionLine = summary.controller === "launchd" ? `LaunchAgent: ${summary.plistPath}` : summary.controller === "systemd" ? `Systemd unit: ${summary.unitPath}` : void 0;
|
|
3099
3350
|
return [
|
|
3100
3351
|
`Controller: ${summary.controller}`,
|
|
3101
3352
|
`Installed: ${summary.installed ? "yes" : "no"}`,
|
|
@@ -3111,7 +3362,7 @@ function formatServiceStatus(summary) {
|
|
|
3111
3362
|
...summary.secrets,
|
|
3112
3363
|
...summary.capabilities,
|
|
3113
3364
|
...summary.serviceHardening,
|
|
3114
|
-
|
|
3365
|
+
...definitionLine ? [definitionLine] : [],
|
|
3115
3366
|
`Stdout log: ${summary.stdoutPath}`,
|
|
3116
3367
|
`Stderr log: ${summary.stderrPath}`,
|
|
3117
3368
|
...summary.controller === "unsupported" ? [unsupportedMessage()] : []
|
|
@@ -3161,11 +3412,23 @@ function formatServiceLogs(options = {}, dependencies = {}) {
|
|
|
3161
3412
|
}
|
|
3162
3413
|
async function runServiceInstallCommand(options, dependencies = {}) {
|
|
3163
3414
|
const paths = installService(options, dependencies);
|
|
3164
|
-
|
|
3415
|
+
const controller = serviceControllerFrom(dependencies);
|
|
3416
|
+
loggerFrom(dependencies).log(`OpenTag service installed: ${controller === "systemd" ? paths.unitPath : paths.plistPath}`);
|
|
3165
3417
|
loggerFrom(dependencies).log("It will start at login. Run `opentag service start` to start it now.");
|
|
3166
3418
|
}
|
|
3419
|
+
async function installAndStartService(options = {}, dependencies = {}) {
|
|
3420
|
+
installService(options, dependencies);
|
|
3421
|
+
const paths = startService(options, dependencies);
|
|
3422
|
+
if (!await waitForServiceLoaded(dependencies)) {
|
|
3423
|
+
throw new Error("OpenTag service start did not leave the service manager running. Run `opentag service status` and `opentag service logs` for details.");
|
|
3424
|
+
}
|
|
3425
|
+
return paths;
|
|
3426
|
+
}
|
|
3167
3427
|
async function runServiceStartCommand(options, dependencies = {}) {
|
|
3168
3428
|
const paths = startService(options, dependencies);
|
|
3429
|
+
if (!await waitForServiceLoaded(dependencies)) {
|
|
3430
|
+
throw new Error("OpenTag service start did not leave the service manager running. Run `opentag service status` and `opentag service logs` for details.");
|
|
3431
|
+
}
|
|
3169
3432
|
loggerFrom(dependencies).log(`OpenTag service started: ${paths.label}`);
|
|
3170
3433
|
}
|
|
3171
3434
|
async function runServiceStopCommand(options, dependencies = {}) {
|
|
@@ -3174,12 +3437,22 @@ async function runServiceStopCommand(options, dependencies = {}) {
|
|
|
3174
3437
|
}
|
|
3175
3438
|
async function runServiceRestartCommand(options, dependencies = {}) {
|
|
3176
3439
|
stopService(options, dependencies);
|
|
3177
|
-
|
|
3440
|
+
await waitForServiceUnloaded(dependencies, { timeoutMs: 1e3 });
|
|
3441
|
+
let paths = startService(options, dependencies);
|
|
3442
|
+
let loaded = await waitForServiceLoaded(dependencies, { timeoutMs: 500 });
|
|
3443
|
+
if (!loaded) {
|
|
3444
|
+
paths = startService(options, dependencies);
|
|
3445
|
+
loaded = await waitForServiceLoaded(dependencies);
|
|
3446
|
+
}
|
|
3447
|
+
if (!loaded) {
|
|
3448
|
+
throw new Error("OpenTag service restart did not leave the service manager running. Run `opentag service status` and `opentag service logs` for details.");
|
|
3449
|
+
}
|
|
3178
3450
|
loggerFrom(dependencies).log(`OpenTag service restarted: ${paths.label}`);
|
|
3179
3451
|
}
|
|
3180
3452
|
async function runServiceUninstallCommand(options, dependencies = {}) {
|
|
3181
3453
|
const paths = uninstallService(options, dependencies);
|
|
3182
|
-
|
|
3454
|
+
const controller = serviceControllerFrom(dependencies);
|
|
3455
|
+
loggerFrom(dependencies).log(`OpenTag service uninstalled: ${controller === "systemd" ? paths.unitPath : paths.plistPath}`);
|
|
3183
3456
|
}
|
|
3184
3457
|
async function runServiceStatusCommand(options, dependencies = {}) {
|
|
3185
3458
|
const summary = await getServiceStatusWithRuntimeReadiness(options, dependencies);
|
|
@@ -4715,16 +4988,54 @@ async function scanLarkPersonalAgent(input = {}, dependencies = {}) {
|
|
|
4715
4988
|
}
|
|
4716
4989
|
|
|
4717
4990
|
// src/commands/setup.ts
|
|
4718
|
-
function startPromptMessage(language) {
|
|
4719
|
-
return language === "zh-CN" ? "\u73B0\u5728\u542F\u52A8 OpenTag\uFF1F" : "Start OpenTag now?";
|
|
4720
|
-
}
|
|
4721
4991
|
function setupCompleteMessage(language) {
|
|
4722
4992
|
return language === "zh-CN" ? "OpenTag \u8BBE\u7F6E\u5B8C\u6210\u3002" : "OpenTag setup complete.";
|
|
4723
4993
|
}
|
|
4724
4994
|
function startingMessage(language) {
|
|
4725
4995
|
return language === "zh-CN" ? "\u6B63\u5728\u542F\u52A8 OpenTag..." : "Starting OpenTag...";
|
|
4726
4996
|
}
|
|
4997
|
+
function serviceStartingMessage(language) {
|
|
4998
|
+
return language === "zh-CN" ? "\u6B63\u5728\u5B89\u88C5\u5E76\u542F\u52A8 OpenTag \u540E\u53F0\u670D\u52A1..." : "Installing and starting the OpenTag background service...";
|
|
4999
|
+
}
|
|
5000
|
+
function serviceStartedMessage(language) {
|
|
5001
|
+
return language === "zh-CN" ? "OpenTag \u8BBE\u7F6E\u5B8C\u6210\uFF0C\u540E\u53F0\u670D\u52A1\u5DF2\u542F\u52A8\u3002" : "OpenTag setup complete. The background service is running.";
|
|
5002
|
+
}
|
|
5003
|
+
function runModePromptMessage(language) {
|
|
5004
|
+
return language === "zh-CN" ? "OpenTag \u8981\u5982\u4F55\u8FD0\u884C\uFF1F" : "How should OpenTag run?";
|
|
5005
|
+
}
|
|
5006
|
+
function runModeOptions(language, serviceSupported) {
|
|
5007
|
+
if (language === "zh-CN") {
|
|
5008
|
+
return [
|
|
5009
|
+
...serviceSupported ? [{ value: "service", label: "\u5173\u95ED\u8FD9\u4E2A\u7EC8\u7AEF\u540E\u7EE7\u7EED\u8FD0\u884C\uFF08\u63A8\u8350\uFF09" }] : [],
|
|
5010
|
+
{ value: "terminal", label: "\u53EA\u5728\u5F53\u524D\u7EC8\u7AEF\u91CC\u8FD0\u884C" },
|
|
5011
|
+
{ value: "later", label: "\u6682\u65F6\u4E0D\u542F\u52A8" }
|
|
5012
|
+
];
|
|
5013
|
+
}
|
|
5014
|
+
return [
|
|
5015
|
+
...serviceSupported ? [{ value: "service", label: "Keep running after I close this terminal (recommended)" }] : [],
|
|
5016
|
+
{ value: "terminal", label: "Run only in this terminal" },
|
|
5017
|
+
{ value: "later", label: "Do not start now" }
|
|
5018
|
+
];
|
|
5019
|
+
}
|
|
5020
|
+
async function collectRunMode(options, prompts, language, platform) {
|
|
5021
|
+
if (options.service) return "service";
|
|
5022
|
+
if (options.start === true) return "terminal";
|
|
5023
|
+
if (options.start === false || options.yes) return "later";
|
|
5024
|
+
const serviceSupported = serviceControllerForPlatform(platform) !== "unsupported";
|
|
5025
|
+
return prompts.select({
|
|
5026
|
+
message: runModePromptMessage(language),
|
|
5027
|
+
initialValue: serviceSupported ? "service" : "terminal",
|
|
5028
|
+
options: runModeOptions(language, serviceSupported)
|
|
5029
|
+
});
|
|
5030
|
+
}
|
|
4727
5031
|
async function runSetupCommand(options, dependencies = {}) {
|
|
5032
|
+
if (options.service && options.start !== void 0) {
|
|
5033
|
+
throw new Error("--service cannot be combined with --start or --no-start.");
|
|
5034
|
+
}
|
|
5035
|
+
const platform = dependencies.platform ?? process.platform;
|
|
5036
|
+
if (options.service && serviceControllerForPlatform(platform) === "unsupported") {
|
|
5037
|
+
throw new Error("OpenTag background service is not supported on this platform. Use `opentag start` to run OpenTag in this terminal.");
|
|
5038
|
+
}
|
|
4728
5039
|
const env = dependencies.env ?? process.env;
|
|
4729
5040
|
const configPath = options.config ?? defaultConfigPath(env);
|
|
4730
5041
|
if (options.yes && existsSync6(configPath) && !options.force) {
|
|
@@ -4744,11 +5055,14 @@ async function runSetupCommand(options, dependencies = {}) {
|
|
|
4744
5055
|
ensurePrivateDirectory(config.state.worktreeRoot);
|
|
4745
5056
|
writeCliConfigAtomic(configPath, config);
|
|
4746
5057
|
prompts.note(formatSetupComplete(config, configPath));
|
|
4747
|
-
const
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
5058
|
+
const runMode = await collectRunMode(options, prompts, config.preferences?.language, platform);
|
|
5059
|
+
if (runMode === "service") {
|
|
5060
|
+
prompts.note(serviceStartingMessage(config.preferences?.language));
|
|
5061
|
+
await (dependencies.startOpenTagService ?? installAndStartService)({ config: configPath });
|
|
5062
|
+
prompts.outro(serviceStartedMessage(config.preferences?.language));
|
|
5063
|
+
return;
|
|
5064
|
+
}
|
|
5065
|
+
if (runMode === "terminal") {
|
|
4752
5066
|
prompts.outro(startingMessage(config.preferences?.language));
|
|
4753
5067
|
await (dependencies.startOpenTag ?? runStartCommand)({ config: configPath });
|
|
4754
5068
|
} else {
|
|
@@ -4772,7 +5086,7 @@ function runCliAction(handler) {
|
|
|
4772
5086
|
};
|
|
4773
5087
|
}
|
|
4774
5088
|
program.name(process.env.OPENTAG_CLI_NAME?.trim() || "opentag").description("OpenTag CLI");
|
|
4775
|
-
program.command("setup").description("Create a local OpenTag config").option("--platform <platform>", "Platform to configure").option("--config <path>", "Config file path").option("--project <path>", "Project checkout path").option("--language <language>", "Setup language: en or zh-CN").option("--executor <executor>", "Default executor: echo, codex, claude-code, or hermes").option("--hermes-command <command>", "Hermes CLI command").option("--hermes-profile <profile>", "Hermes profile").option("--hermes-profile-template <template>", "Hermes profile template").option("--agent-profile <profile>", "Executor-neutral agent session profile").option("--agent-profile-template <template>", "Executor-neutral agent session profile template").option("--lark-setup <method>", "Lark setup method: saved, scan, or manual").option("--lark-app-id <id>", "Lark app id").option("--lark-app-secret <secret>", "Lark app secret").option("--tenant <tenant>", "Manual Lark / Feishu tenant: feishu or lark").option("--lark-bot-open-id <openId>", "Lark bot open id for group mentions").option("--slack-mode <mode>", "Slack connection mode: socket_mode or events_api").option("--slack-app-token <token>", "Slack app-level token for Socket Mode").option("--slack-signing-secret <secret>", "Slack signing secret").option("--slack-bot-token <token>", "Slack bot user OAuth token").option("--slack-app-id <id>", "Slack app id").option("--slack-team-id <id>", "Slack team id").option("--slack-channel-id <id>", "Slack channel id").option("--slack-port <port>", "Local Slack Events API port").option("--github-token <token>", "GitHub token for comments and apply-1 pull requests").option("--github-webhook-secret <secret>", "GitHub webhook secret; generated when omitted").option("--github-repository <ownerRepo>", "GitHub repository as owner/repo").option("--github-webhook-path <path>", "GitHub webhook path").option("--github-port <port>", "Local GitHub webhook port").option("--github-auto-create-pr", "Create pull requests immediately after runs").option("--no-github-auto-create-pr", "Use the default apply-1 pull request flow").option("--binding <method>", "Binding method: default_project or bind_later").option("--force", "Overwrite an existing config").option("--start", "Start OpenTag immediately after setup").option("--no-start", "Do not ask to start OpenTag after setup").option("-y, --yes", "Skip setup confirmation").action(runCliAction(runSetupCommand));
|
|
5089
|
+
program.command("setup").description("Create a local OpenTag config").option("--platform <platform>", "Platform to configure").option("--config <path>", "Config file path").option("--project <path>", "Project checkout path").option("--language <language>", "Setup language: en or zh-CN").option("--executor <executor>", "Default executor: echo, codex, claude-code, or hermes").option("--hermes-command <command>", "Hermes CLI command").option("--hermes-profile <profile>", "Hermes profile").option("--hermes-profile-template <template>", "Hermes profile template").option("--agent-profile <profile>", "Executor-neutral agent session profile").option("--agent-profile-template <template>", "Executor-neutral agent session profile template").option("--lark-setup <method>", "Lark setup method: saved, scan, or manual").option("--lark-app-id <id>", "Lark app id").option("--lark-app-secret <secret>", "Lark app secret").option("--tenant <tenant>", "Manual Lark / Feishu tenant: feishu or lark").option("--lark-bot-open-id <openId>", "Lark bot open id for group mentions").option("--slack-mode <mode>", "Slack connection mode: socket_mode or events_api").option("--slack-app-token <token>", "Slack app-level token for Socket Mode").option("--slack-signing-secret <secret>", "Slack signing secret").option("--slack-bot-token <token>", "Slack bot user OAuth token").option("--slack-app-id <id>", "Slack app id").option("--slack-team-id <id>", "Slack team id").option("--slack-channel-id <id>", "Slack channel id").option("--slack-port <port>", "Local Slack Events API port").option("--github-token <token>", "GitHub token for comments and apply-1 pull requests").option("--github-webhook-secret <secret>", "GitHub webhook secret; generated when omitted").option("--github-repository <ownerRepo>", "GitHub repository as owner/repo").option("--github-webhook-path <path>", "GitHub webhook path").option("--github-port <port>", "Local GitHub webhook port").option("--github-auto-create-pr", "Create pull requests immediately after runs").option("--no-github-auto-create-pr", "Use the default apply-1 pull request flow").option("--binding <method>", "Binding method: default_project or bind_later").option("--force", "Overwrite an existing config").option("--start", "Start OpenTag immediately after setup").option("--no-start", "Do not ask to start OpenTag after setup").option("--service", "Install and start OpenTag as a background service after setup").option("-y, --yes", "Skip setup confirmation").action(runCliAction(runSetupCommand));
|
|
4776
5090
|
program.command("pair").description("Pair this local runner with a remote relay").option("--config <path>", "Config file path").option("--relay <url>", "Remote relay dispatcher URL").option("--no-register", "Update config without registering runner and project targets").action(runCliAction(runPairCommand));
|
|
4777
5091
|
program.command("start").description("Start the local OpenTag stack").option("--config <path>", "Config file path").action(runCliAction(runStartCommand));
|
|
4778
5092
|
program.command("status").description("Show the local OpenTag status").option("--config <path>", "Config file path").option("--run <runId>", "Show audit details for one run").option("--channel <provider:account/conversation>", "Show active run and queued follow-ups for one source container").action(runCliAction(runStatusCommand));
|
|
@@ -4781,7 +5095,7 @@ program.command("doctor").description("Check dispatcher, bindings, checkouts, an
|
|
|
4781
5095
|
program.command("ingest").description("Ingest a local external agent progress or completion event").option("--config <path>", "Config file path").requiredOption("--run <runId>", "OpenTag run id").requiredOption("--event <event>", "Event: progress, post_llm_call, before_agent_finalize, agent_end, failed, cancelled, timed_out, or interrupted").option("--source <source>", "External agent runtime source label").option("--message <message>", "Progress or completion summary").option("--type <type>", "Progress event type").option("--idempotency-key <key>", "Stable replay-protection key for retrying the same progress event").option("--result-json <json>", "Complete run with an OpenTagRunResult JSON object").option("--conclusion <conclusion>", "Completion conclusion when --result-json is omitted").option("--summary <summary>", "Completion summary when --result-json is omitted").action(runCliAction(runIngestCommand));
|
|
4782
5096
|
program.command("ingest-template").description("Print a shell template or manifest for local external agent hook ingest").option("--source <source>", "External agent runtime source label").option("--command <command>", "OpenTag CLI command to use in the template").option("--format <format>", "Template format: shell or manifest").action(runCliAction(runIngestTemplateCommand));
|
|
4783
5097
|
var serviceCommand = program.command("service").description("Install and control the OpenTag background service");
|
|
4784
|
-
serviceCommand.command("install").description("Install the OpenTag
|
|
5098
|
+
serviceCommand.command("install").description("Install the OpenTag background service").option("--config <path>", "Config file path").option("--max-request-body-bytes <bytes>", "Persist dispatcher request body limit in the service definition").option("--rate-limit-window-ms <ms>", "Persist dispatcher rate-limit window in the service definition").option("--rate-limit-max-requests <n>", "Persist dispatcher rate-limit max requests in the service definition").option("--rate-limit-disabled", "Persist an explicit disabled dispatcher rate-limit state in the service definition").action(runCliAction(runServiceInstallCommand));
|
|
4785
5099
|
serviceCommand.command("start").description("Start the OpenTag background service").option("--config <path>", "Config file path").action(runCliAction(runServiceStartCommand));
|
|
4786
5100
|
serviceCommand.command("stop").description("Stop the OpenTag background service").option("--config <path>", "Config file path").action(runCliAction(runServiceStopCommand));
|
|
4787
5101
|
serviceCommand.command("restart").description("Restart the OpenTag background service").option("--config <path>", "Config file path").action(runCliAction(runServiceRestartCommand));
|