@lazyingart/agintiflow 0.20.48 → 0.20.49
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/docs/skillmesh.md +43 -0
- package/package.json +1 -1
- package/references/skill-mesh-sharing-design.md +1 -0
- package/scripts/smoke-skillmesh.js +11 -0
- package/src/cli.js +1 -1
- package/src/skillmesh.js +227 -1
package/docs/skillmesh.md
CHANGED
|
@@ -86,6 +86,49 @@ Endpoints:
|
|
|
86
86
|
|
|
87
87
|
The relay stores only validated skill packs and metadata in its data directory. It rejects raw sessions, unsafe paths, secret-like text, unsupported schemas, overlarge packs, unsigned packs, and packs that do not declare the strict privacy contract.
|
|
88
88
|
|
|
89
|
+
## Service Mode
|
|
90
|
+
|
|
91
|
+
A relay started in `tmux` or a normal shell will stop after reboot. For a node that should keep listening after reboot, install it as a service.
|
|
92
|
+
|
|
93
|
+
System service, recommended for public relay nodes:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
aginti skillmesh service install \
|
|
97
|
+
--host 0.0.0.0 \
|
|
98
|
+
--port 7377 \
|
|
99
|
+
--data ~/.aginti-skill-relay \
|
|
100
|
+
--public-url http://YOUR_PUBLIC_HOST:7377
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This uses `sudo -n` to create and enable `/etc/systemd/system/aginti-skill-relay.service`, so it requires sudo permission. It runs the relay as the current user and restarts on failure and after reboot.
|
|
104
|
+
|
|
105
|
+
User service, useful when sudo is not available:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
aginti skillmesh service install --user --linger \
|
|
109
|
+
--host 0.0.0.0 \
|
|
110
|
+
--port 7377 \
|
|
111
|
+
--data ~/.aginti-skill-relay \
|
|
112
|
+
--public-url http://YOUR_PUBLIC_HOST:7377
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A user service can run without sudo, but boot persistence requires lingering:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
sudo loginctl enable-linger $(whoami)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Service commands:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
aginti skillmesh service status
|
|
125
|
+
aginti skillmesh service restart
|
|
126
|
+
aginti skillmesh service stop
|
|
127
|
+
aginti skillmesh service uninstall
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Use `--user` with those commands for the user-service scope.
|
|
131
|
+
|
|
89
132
|
## Storage
|
|
90
133
|
|
|
91
134
|
Local client storage:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.49",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -11,6 +11,7 @@ Implementation note as of v0.20.48:
|
|
|
11
11
|
- `aginti skillmesh` and `/skillmesh` exist as the first conservative MVP.
|
|
12
12
|
- The shipped implementation uses signed JSON skill packs first, not tarballs, to avoid archive traversal and executable-file risk.
|
|
13
13
|
- Relay nodes can run without model/API keys via `aginti skillmesh serve`.
|
|
14
|
+
- Relay nodes can persist after reboot with `aginti skillmesh service install`; system services require sudo, while user services need systemd lingering for boot persistence.
|
|
14
15
|
- Community imports install disabled by default and cannot override built-in skills.
|
|
15
16
|
- Sync is explicit and metadata-first; no continuous background polling is enabled yet.
|
|
16
17
|
|
|
@@ -4,6 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import {
|
|
6
6
|
buildSkillPackFromMarkdown,
|
|
7
|
+
buildSkillMeshServiceUnit,
|
|
7
8
|
enableSkillMeshSkill,
|
|
8
9
|
installSkillPack,
|
|
9
10
|
listInstalledSkillMeshSkills,
|
|
@@ -63,6 +64,15 @@ assert(!listSkills({ includeBody: false }).some((skill) => skill.id === "mesh-an
|
|
|
63
64
|
await setSkillMeshMode("share");
|
|
64
65
|
const config = await loadSkillMeshConfig();
|
|
65
66
|
assert(config.mode === "share", "share mode should persist");
|
|
67
|
+
const serviceUnit = buildSkillMeshServiceUnit({
|
|
68
|
+
dataDir: path.join(tempRoot, "relay-service"),
|
|
69
|
+
runScript: path.join(tempRoot, "relay-service", "aginti-skill-relay.run.sh"),
|
|
70
|
+
scope: "system",
|
|
71
|
+
user: "aginti-test",
|
|
72
|
+
});
|
|
73
|
+
assert(serviceUnit.includes("Restart=on-failure"), "systemd unit should restart relay");
|
|
74
|
+
assert(serviceUnit.includes("WantedBy=multi-user.target"), "systemd unit should start at boot in system scope");
|
|
75
|
+
assert(serviceUnit.includes("ExecStart="), "systemd unit should include ExecStart");
|
|
66
76
|
|
|
67
77
|
const secretMarkdown = skillMarkdown.replace("mesh-android-screenshot", "mesh-secret-test") + "\nOPENAI_API_KEY=sk-thisShouldBeRejected1234567890\n";
|
|
68
78
|
let rejected = false;
|
|
@@ -109,6 +119,7 @@ console.log(
|
|
|
109
119
|
"relay-node-list",
|
|
110
120
|
"metadata-sync",
|
|
111
121
|
"soft-unreachable-node",
|
|
122
|
+
"systemd-service-unit",
|
|
112
123
|
],
|
|
113
124
|
},
|
|
114
125
|
null,
|
package/src/cli.js
CHANGED
|
@@ -383,7 +383,7 @@ export function parseArgs(argv) {
|
|
|
383
383
|
|
|
384
384
|
function printUsage() {
|
|
385
385
|
console.log(
|
|
386
|
-
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti update OR aginti models OR aginti skills [query] OR aginti skillmesh [status|off|record|share|sync|serve] OR aginti housekeeping [--json] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [--all-sessions] [latest|<session-id>] ["prompt"] OR aginti --remove-empty-sessions OR aginti --remove-sessions OR aginti queue <session-id> "message" OR aginti [--no-auto-update] [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
|
|
386
|
+
'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti update OR aginti models OR aginti skills [query] OR aginti skillmesh [status|off|record|share|sync|serve|service] OR aginti housekeeping [--json] OR aginti auth [deepseek|openai|qwen|venice|grsai] OR aginti resume [--all-sessions] [latest|<session-id>] ["prompt"] OR aginti --remove-empty-sessions OR aginti --remove-sessions OR aginti queue <session-id> "message" OR aginti [--no-auto-update] [--language en|ja|zh-Hans|zh-Hant|ko|fr|es|ar|vi|de|ru] [--image] [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|qwen|venice|mock] [--model MODEL] [--route-model MODEL] [--main-model MODEL] [--spare-model MODEL --spare-reasoning medium] [--aux-provider grsai|venice --aux-model MODEL] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex --wrapper-model gpt-5.5] [--list-models|--list-routes] "your task"'
|
|
387
387
|
);
|
|
388
388
|
console.log(`Languages: ${["en", "ja", "zh-Hans", "zh-Hant", "ko", "fr", "es", "ar", "vi", "de", "ru"].map((code) => `${code}=${languageLabel(code)}`).join(", ")}`);
|
|
389
389
|
}
|
package/src/skillmesh.js
CHANGED
|
@@ -2,8 +2,11 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import fsSync from "node:fs";
|
|
4
4
|
import http from "node:http";
|
|
5
|
+
import os from "node:os";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import process from "node:process";
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
7
10
|
import express from "express";
|
|
8
11
|
import { agintiflowHome } from "./session-index.js";
|
|
9
12
|
import { loadDatabaseSync } from "./sqlite.js";
|
|
@@ -16,6 +19,8 @@ const MAX_SKILLS_PER_PACK = 5;
|
|
|
16
19
|
const MAX_SKILL_BYTES = 80 * 1024;
|
|
17
20
|
const MAX_FEED_PACKS = 200;
|
|
18
21
|
const DEFAULT_NODE_URL = "https://skills.flow.lazying.art";
|
|
22
|
+
const DEFAULT_SERVICE_NAME = "aginti-skill-relay";
|
|
23
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
19
24
|
const SAFE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{1,80}$/;
|
|
20
25
|
const FORBIDDEN_PATH_PARTS = [
|
|
21
26
|
".env",
|
|
@@ -921,6 +926,195 @@ export async function startSkillMeshRelay({
|
|
|
921
926
|
};
|
|
922
927
|
}
|
|
923
928
|
|
|
929
|
+
function expandHome(value = "") {
|
|
930
|
+
const text = String(value || "");
|
|
931
|
+
if (text === "~") return os.homedir();
|
|
932
|
+
if (text.startsWith("~/")) return path.join(os.homedir(), text.slice(2));
|
|
933
|
+
return text;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function shellQuote(value = "") {
|
|
937
|
+
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function currentUserName() {
|
|
941
|
+
return process.env.SUDO_USER || process.env.USER || os.userInfo().username || "aginti-relay";
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function servicePaths({ dataDir = "", name = DEFAULT_SERVICE_NAME } = {}) {
|
|
945
|
+
const root = path.resolve(expandHome(dataDir || "~/.aginti-skill-relay"));
|
|
946
|
+
return {
|
|
947
|
+
dataDir: root,
|
|
948
|
+
runScript: path.join(root, `${name}.run.sh`),
|
|
949
|
+
logDir: path.join(root, "logs"),
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
function relayServeArgs({ host = "127.0.0.1", port = 7377, dataDir = "", publicUrl = "", role = "major", noUploads = false } = {}) {
|
|
954
|
+
const args = [
|
|
955
|
+
process.execPath,
|
|
956
|
+
path.join(PACKAGE_ROOT, "bin", "aginti-cli.js"),
|
|
957
|
+
"skillmesh",
|
|
958
|
+
"serve",
|
|
959
|
+
"--role",
|
|
960
|
+
role,
|
|
961
|
+
"--host",
|
|
962
|
+
host,
|
|
963
|
+
"--port",
|
|
964
|
+
String(port),
|
|
965
|
+
"--data",
|
|
966
|
+
path.resolve(expandHome(dataDir || "~/.aginti-skill-relay")),
|
|
967
|
+
];
|
|
968
|
+
if (publicUrl) args.push("--public-url", publicUrl);
|
|
969
|
+
if (noUploads) args.push("--no-uploads");
|
|
970
|
+
return args;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
export function buildSkillMeshServiceUnit({
|
|
974
|
+
name = DEFAULT_SERVICE_NAME,
|
|
975
|
+
user = currentUserName(),
|
|
976
|
+
dataDir = "",
|
|
977
|
+
runScript = "",
|
|
978
|
+
scope = "system",
|
|
979
|
+
} = {}) {
|
|
980
|
+
const paths = servicePaths({ dataDir, name });
|
|
981
|
+
const script = runScript || paths.runScript;
|
|
982
|
+
const lines = [
|
|
983
|
+
"[Unit]",
|
|
984
|
+
"Description=AgInTi Skill Mesh Relay",
|
|
985
|
+
"After=network-online.target",
|
|
986
|
+
"Wants=network-online.target",
|
|
987
|
+
"",
|
|
988
|
+
"[Service]",
|
|
989
|
+
"Type=simple",
|
|
990
|
+
];
|
|
991
|
+
if (scope === "system") {
|
|
992
|
+
lines.push(`User=${user}`);
|
|
993
|
+
}
|
|
994
|
+
lines.push(
|
|
995
|
+
`WorkingDirectory=${paths.dataDir}`,
|
|
996
|
+
"Environment=NODE_ENV=production",
|
|
997
|
+
`ExecStart=${script}`,
|
|
998
|
+
"Restart=on-failure",
|
|
999
|
+
"RestartSec=5",
|
|
1000
|
+
"NoNewPrivileges=true",
|
|
1001
|
+
"PrivateTmp=true",
|
|
1002
|
+
"ProtectSystem=full",
|
|
1003
|
+
`ReadWritePaths=${paths.dataDir}`,
|
|
1004
|
+
"",
|
|
1005
|
+
"[Install]",
|
|
1006
|
+
scope === "system" ? "WantedBy=multi-user.target" : "WantedBy=default.target",
|
|
1007
|
+
""
|
|
1008
|
+
);
|
|
1009
|
+
return lines.join("\n");
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
async function writeRelayRunScript(options = {}) {
|
|
1013
|
+
const name = options.name || DEFAULT_SERVICE_NAME;
|
|
1014
|
+
const paths = servicePaths({ dataDir: options.dataDir, name });
|
|
1015
|
+
await fs.mkdir(paths.logDir, { recursive: true });
|
|
1016
|
+
const args = relayServeArgs({ ...options, dataDir: paths.dataDir });
|
|
1017
|
+
const logPath = path.join(paths.logDir, "relay.log");
|
|
1018
|
+
const script = [
|
|
1019
|
+
"#!/usr/bin/env bash",
|
|
1020
|
+
"set -euo pipefail",
|
|
1021
|
+
`mkdir -p ${shellQuote(paths.logDir)}`,
|
|
1022
|
+
`exec ${args.map(shellQuote).join(" ")} 2>&1 | tee -a ${shellQuote(logPath)}`,
|
|
1023
|
+
"",
|
|
1024
|
+
].join("\n");
|
|
1025
|
+
await fs.writeFile(paths.runScript, script, "utf8");
|
|
1026
|
+
await fs.chmod(paths.runScript, 0o755);
|
|
1027
|
+
return { ...paths, runScript: paths.runScript, logPath };
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function runCommand(command, args = [], { input = undefined, stdio = "pipe" } = {}) {
|
|
1031
|
+
return execFileSync(command, args, {
|
|
1032
|
+
input,
|
|
1033
|
+
encoding: "utf8",
|
|
1034
|
+
stdio: input === undefined ? stdio : ["pipe", "pipe", "pipe"],
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
function runSudo(args = [], { input = undefined } = {}) {
|
|
1039
|
+
return runCommand("sudo", ["-n", ...args], { input });
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function systemctl(scope = "system", args = []) {
|
|
1043
|
+
return scope === "user" ? runCommand("systemctl", ["--user", ...args]) : runSudo(["systemctl", ...args]);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function serviceNameFromOptions(options = {}) {
|
|
1047
|
+
const name = String(options.name || DEFAULT_SERVICE_NAME).trim();
|
|
1048
|
+
if (!/^[A-Za-z0-9_.@-]+$/.test(name)) throw new Error(`Invalid service name: ${name}`);
|
|
1049
|
+
return name.endsWith(".service") ? name.slice(0, -8) : name;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
export async function installSkillMeshService(options = {}) {
|
|
1053
|
+
if (process.platform !== "linux") {
|
|
1054
|
+
throw new Error("Skill Mesh service install currently supports Linux systemd. Use a process manager on this OS.");
|
|
1055
|
+
}
|
|
1056
|
+
const scope = options.user ? "user" : "system";
|
|
1057
|
+
const name = serviceNameFromOptions(options);
|
|
1058
|
+
const serviceFile = `${name}.service`;
|
|
1059
|
+
const paths = await writeRelayRunScript({ ...options, name });
|
|
1060
|
+
const unit = buildSkillMeshServiceUnit({
|
|
1061
|
+
name,
|
|
1062
|
+
user: options.serviceUser || currentUserName(),
|
|
1063
|
+
dataDir: paths.dataDir,
|
|
1064
|
+
runScript: paths.runScript,
|
|
1065
|
+
scope,
|
|
1066
|
+
});
|
|
1067
|
+
if (scope === "user") {
|
|
1068
|
+
const unitDir = path.join(os.homedir(), ".config", "systemd", "user");
|
|
1069
|
+
const unitPath = path.join(unitDir, serviceFile);
|
|
1070
|
+
await fs.mkdir(unitDir, { recursive: true });
|
|
1071
|
+
await fs.writeFile(unitPath, unit, "utf8");
|
|
1072
|
+
runCommand("systemctl", ["--user", "daemon-reload"]);
|
|
1073
|
+
runCommand("systemctl", ["--user", "enable", "--now", serviceFile]);
|
|
1074
|
+
if (options.linger) {
|
|
1075
|
+
try {
|
|
1076
|
+
runSudo(["loginctl", "enable-linger", currentUserName()]);
|
|
1077
|
+
} catch {
|
|
1078
|
+
// Linger is optional; status output tells the user how to enable boot persistence.
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return { scope, serviceFile, unitPath, ...paths };
|
|
1082
|
+
}
|
|
1083
|
+
const unitPath = `/etc/systemd/system/${serviceFile}`;
|
|
1084
|
+
runSudo(["tee", unitPath], { input: unit });
|
|
1085
|
+
runSudo(["systemctl", "daemon-reload"]);
|
|
1086
|
+
runSudo(["systemctl", "enable", "--now", serviceFile]);
|
|
1087
|
+
return { scope, serviceFile, unitPath, ...paths };
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
export async function manageSkillMeshService(action = "status", options = {}) {
|
|
1091
|
+
const scope = options.user ? "user" : "system";
|
|
1092
|
+
const name = serviceNameFromOptions(options);
|
|
1093
|
+
const serviceFile = `${name}.service`;
|
|
1094
|
+
if (action === "install") return installSkillMeshService(options);
|
|
1095
|
+
if (action === "uninstall" || action === "remove") {
|
|
1096
|
+
try {
|
|
1097
|
+
systemctl(scope, ["disable", "--now", serviceFile]);
|
|
1098
|
+
} catch {
|
|
1099
|
+
// Continue removing unit files even if the service is already absent.
|
|
1100
|
+
}
|
|
1101
|
+
if (scope === "user") {
|
|
1102
|
+
await fs.rm(path.join(os.homedir(), ".config", "systemd", "user", serviceFile), { force: true });
|
|
1103
|
+
runCommand("systemctl", ["--user", "daemon-reload"]);
|
|
1104
|
+
} else {
|
|
1105
|
+
runSudo(["rm", "-f", `/etc/systemd/system/${serviceFile}`]);
|
|
1106
|
+
runSudo(["systemctl", "daemon-reload"]);
|
|
1107
|
+
}
|
|
1108
|
+
return { scope, serviceFile, removed: true };
|
|
1109
|
+
}
|
|
1110
|
+
if (["start", "stop", "restart", "enable", "disable"].includes(action)) {
|
|
1111
|
+
systemctl(scope, [action, action === "enable" || action === "disable" ? "--now" : "", serviceFile].filter(Boolean));
|
|
1112
|
+
return { scope, serviceFile, action };
|
|
1113
|
+
}
|
|
1114
|
+
const output = systemctl(scope, ["status", "--no-pager", serviceFile]);
|
|
1115
|
+
return { scope, serviceFile, status: output };
|
|
1116
|
+
}
|
|
1117
|
+
|
|
924
1118
|
function parseCliOptions(argv = []) {
|
|
925
1119
|
const options = { _: [] };
|
|
926
1120
|
for (let i = 0; i < argv.length; i += 1) {
|
|
@@ -993,6 +1187,38 @@ export async function handleSkillMeshCommand(argv = []) {
|
|
|
993
1187
|
}
|
|
994
1188
|
throw new Error("Usage: aginti skillmesh node add <name> <url> OR aginti skillmesh node remove <name-or-url>");
|
|
995
1189
|
}
|
|
1190
|
+
if (command === "service") {
|
|
1191
|
+
const action = String(options._[0] || "status").toLowerCase();
|
|
1192
|
+
const serviceOptions = {
|
|
1193
|
+
name: options.name || DEFAULT_SERVICE_NAME,
|
|
1194
|
+
user: Boolean(options.user),
|
|
1195
|
+
linger: Boolean(options.linger),
|
|
1196
|
+
serviceUser: options["service-user"] || currentUserName(),
|
|
1197
|
+
host: options.host || "127.0.0.1",
|
|
1198
|
+
port: Number(options.port || 7377),
|
|
1199
|
+
dataDir: options.data || "~/.aginti-skill-relay",
|
|
1200
|
+
publicUrl: options["public-url"] || "",
|
|
1201
|
+
role: options.role || "major",
|
|
1202
|
+
noUploads: Boolean(options["no-uploads"]),
|
|
1203
|
+
};
|
|
1204
|
+
const result = await manageSkillMeshService(action, serviceOptions);
|
|
1205
|
+
if (action === "status") {
|
|
1206
|
+
console.log(result.status || `${result.serviceFile} status unavailable`);
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
if (action === "install") {
|
|
1210
|
+
console.log(`installed ${result.scope} service ${result.serviceFile}`);
|
|
1211
|
+
console.log(`unit=${result.unitPath}`);
|
|
1212
|
+
console.log(`run=${result.runScript}`);
|
|
1213
|
+
console.log(`data=${result.dataDir}`);
|
|
1214
|
+
if (result.scope === "user") {
|
|
1215
|
+
console.log("For reboot persistence of a user service, enable linger: sudo loginctl enable-linger $(whoami)");
|
|
1216
|
+
}
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
console.log(`${action} ${result.scope} service ${result.serviceFile}`);
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
996
1222
|
if (command === "export") {
|
|
997
1223
|
const skillId = options._[0] || "";
|
|
998
1224
|
if (!skillId) throw new Error("Usage: aginti skillmesh export <skill-id> [--out file.skillpack.json]");
|
|
@@ -1053,6 +1279,6 @@ export async function handleSkillMeshCommand(argv = []) {
|
|
|
1053
1279
|
return;
|
|
1054
1280
|
}
|
|
1055
1281
|
throw new Error(
|
|
1056
|
-
"Usage: aginti skillmesh [status|off|record|share|nodes|export|import|enable|disable-skill|sync|submit|serve]"
|
|
1282
|
+
"Usage: aginti skillmesh [status|off|record|share|nodes|node|service|export|import|enable|disable-skill|sync|submit|serve]"
|
|
1057
1283
|
);
|
|
1058
1284
|
}
|