@kuznai/inception-engine 0.2.0 → 0.2.4
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 +3 -1
- package/dist/config/manifest.js +7 -0
- package/dist/core/deploy.js +5 -0
- package/dist/core/resolve.js +51 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -153,7 +153,9 @@ If an agent isn't detected, its skills are skipped. Use `--agents` to override d
|
|
|
153
153
|
|
|
154
154
|
## Running with Privilege Escalation
|
|
155
155
|
|
|
156
|
-
The tool works without elevated privileges. If run with `sudo` on POSIX systems, it
|
|
156
|
+
The tool works without elevated privileges. If run with `sudo` on POSIX systems, it looks up the real user's home directory from the OS directory services (`getent passwd` on Linux, `dscl` on macOS, `/etc/passwd` as a universal fallback) so skills are deployed to the correct location regardless of where home directories are stored — standard `/home/<user>`, LDAP/NIS paths, enterprise layouts, or otherwise.
|
|
157
|
+
|
|
158
|
+
If the real home cannot be determined, the tool exits with an error rather than silently deploying to a guessed path.
|
|
157
159
|
|
|
158
160
|
On Windows, `os.homedir()` correctly resolves even in elevated PowerShell or cmd.
|
|
159
161
|
|
package/dist/config/manifest.js
CHANGED
|
@@ -36,9 +36,16 @@ function validateManifest(data, filePath) {
|
|
|
36
36
|
if (typeof skill.name !== "string" || skill.name.length === 0) {
|
|
37
37
|
throw new UserError(`${filePath}: skills[${i}].name must be a non-empty string`);
|
|
38
38
|
}
|
|
39
|
+
const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
40
|
+
if (!SAFE_NAME_RE.test(skill.name)) {
|
|
41
|
+
throw new UserError(`${filePath}: skills[${i}].name must contain only letters, digits, hyphens, underscores, and dots, and must not start with a dot`);
|
|
42
|
+
}
|
|
39
43
|
if (typeof skill.path !== "string" || skill.path.length === 0) {
|
|
40
44
|
throw new UserError(`${filePath}: skills[${i}].path must be a non-empty string`);
|
|
41
45
|
}
|
|
46
|
+
if (path.isAbsolute(skill.path)) {
|
|
47
|
+
throw new UserError(`${filePath}: skills[${i}].path must be a relative path`);
|
|
48
|
+
}
|
|
42
49
|
if (!Array.isArray(skill.agents) || skill.agents.length === 0) {
|
|
43
50
|
throw new UserError(`${filePath}: skills[${i}].agents must be a non-empty array`);
|
|
44
51
|
}
|
package/dist/core/deploy.js
CHANGED
|
@@ -2,11 +2,16 @@ import { existsSync, lstatSync, mkdirSync, rmSync, symlinkSync, cpSync, unlinkSy
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { AGENT_REGISTRY } from "../config/agents.js";
|
|
4
4
|
import { resolveAgentSkillPath, getDeployMethod } from "./resolve.js";
|
|
5
|
+
import { UserError } from "../errors.js";
|
|
5
6
|
export function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
6
7
|
const method = getDeployMethod();
|
|
7
8
|
const actions = [];
|
|
9
|
+
const resolvedSourceDir = path.resolve(sourceDir);
|
|
8
10
|
for (const skill of manifest.skills) {
|
|
9
11
|
const source = path.resolve(sourceDir, skill.path);
|
|
12
|
+
if (source !== resolvedSourceDir && !source.startsWith(resolvedSourceDir + path.sep)) {
|
|
13
|
+
throw new UserError(`Skill path "${skill.path}" resolves outside the repository root: ${source}`);
|
|
14
|
+
}
|
|
10
15
|
for (const agentId of skill.agents) {
|
|
11
16
|
if (!detectedAgents.includes(agentId))
|
|
12
17
|
continue;
|
package/dist/core/resolve.js
CHANGED
|
@@ -1,16 +1,65 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
1
3
|
import os from "node:os";
|
|
2
4
|
import path from "node:path";
|
|
5
|
+
import { UserError } from "../errors.js";
|
|
3
6
|
export function resolveHome() {
|
|
4
7
|
if (process.platform === "win32") {
|
|
5
8
|
return os.homedir();
|
|
6
9
|
}
|
|
7
10
|
const sudoUser = process.env["SUDO_USER"];
|
|
8
11
|
if (sudoUser) {
|
|
9
|
-
|
|
10
|
-
return path.join(base, sudoUser);
|
|
12
|
+
return lookupHomeForUser(sudoUser);
|
|
11
13
|
}
|
|
12
14
|
return os.homedir();
|
|
13
15
|
}
|
|
16
|
+
function lookupHomeForUser(username) {
|
|
17
|
+
// Method 1: getent passwd (Linux/POSIX — handles LDAP, NIS, local via NSS)
|
|
18
|
+
if (process.platform !== "darwin") {
|
|
19
|
+
try {
|
|
20
|
+
const out = execFileSync("getent", ["passwd", username], {
|
|
21
|
+
encoding: "utf8",
|
|
22
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
23
|
+
}).trim();
|
|
24
|
+
const home = out.split(":")[5];
|
|
25
|
+
if (typeof home === "string" && home.startsWith("/"))
|
|
26
|
+
return home;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// getent unavailable or user not found — try next method
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Method 2: dscl (macOS directory services)
|
|
33
|
+
if (process.platform === "darwin") {
|
|
34
|
+
try {
|
|
35
|
+
const out = execFileSync("dscl", [".", "-read", `/Users/${username}`, "NFSHomeDirectory"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
36
|
+
const home = out.replace(/^NFSHomeDirectory:\s*/, "").trim();
|
|
37
|
+
if (home.startsWith("/"))
|
|
38
|
+
return home;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// dscl unavailable or user record not found — try next method
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Method 3: parse /etc/passwd directly (universal POSIX fallback)
|
|
45
|
+
try {
|
|
46
|
+
const passwd = readFileSync("/etc/passwd", "utf8");
|
|
47
|
+
for (const line of passwd.split("\n")) {
|
|
48
|
+
const parts = line.split(":");
|
|
49
|
+
if (parts[0] === username) {
|
|
50
|
+
const home = parts[5];
|
|
51
|
+
if (typeof home === "string" && home.startsWith("/"))
|
|
52
|
+
return home;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// /etc/passwd unavailable — fall through to error
|
|
58
|
+
}
|
|
59
|
+
throw new UserError(`Cannot determine home directory for user "${username}". ` +
|
|
60
|
+
`Tried getent, dscl, and /etc/passwd. ` +
|
|
61
|
+
`Run without sudo, or set HOME to the correct path before invoking with sudo.`);
|
|
62
|
+
}
|
|
14
63
|
export function getPlatformKey() {
|
|
15
64
|
return process.platform === "win32" ? "windows" : "posix";
|
|
16
65
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kuznai/inception-engine",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Deploy AI agent skills from a git repo to user home directories",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"author": "Damian
|
|
6
|
+
"author": "Damian Piątkowski",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/KuzniAI/inception-engine.git"
|